您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在微博博主页面添加复制按钮,方便复制博文和评论,支持复制链接,自动展开折叠内容,提取链接,短链接自动展开,保留格式换行,优化纯文本复制
// ==UserScript== // @name 微博复制助手 // @namespace http://tampermonkey.net/ // @version 0.29 // @description 在微博博主页面添加复制按钮,方便复制博文和评论,支持复制链接,自动展开折叠内容,提取链接,短链接自动展开,保留格式换行,优化纯文本复制 // @author You // @match https://weibo.com/u/* // @match https://weibo.com/* // @grant GM_addStyle // @grant GM_setClipboard // @grant GM_xmlhttpRequest // @connect t.cn // @connect weibo.cn // @connect sinaurl.cn // @connect * // @license MIT // ==/UserScript== (function() { 'use strict'; // 添加样式 GM_addStyle(` .copy-btn, .extract-links-btn, .copy-format-btn { display: inline-block !important; /* 强制显示我们的按钮 */ margin-left: 10px; padding: 2px 8px; background-color: #ff8140; color: white; border-radius: 3px; cursor: pointer; font-size: 12px; border: none; visibility: visible !important; /* 确保我们的按钮可见 */ opacity: 1 !important; /* 确保我们的按钮不透明 */ } .copy-btn:hover, .extract-links-btn:hover, .copy-format-btn:hover { background-color: #f26d31; } /* 链接弹窗样式 */ .links-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: white; border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); z-index: 10000; max-width: 80%; max-height: 80%; overflow: auto; padding: 15px; display: none; /* 默认隐藏 */ } .links-modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #eee; } .links-modal-title { font-size: 16px; font-weight: bold; } .links-modal-close { cursor: pointer; font-size: 18px; color: #666; } .links-modal-content { margin-bottom: 10px; } .links-list { list-style: none; padding: 0; margin: 0; } .links-list li { margin-bottom: 12px; padding: 8px; border-bottom: 1px solid #f0f0f0; word-break: break-all; } .links-list li:last-child { border-bottom: none; } .links-list a { color: #ff8140; text-decoration: none; word-break: break-all; display: block; } .links-list a:hover { text-decoration: underline; } .link-original { color: #666; font-size: 12px; margin-top: 4px; display: block; } .link-loading { color: #999; font-style: italic; font-size: 12px; } .links-modal-footer { display: flex; justify-content: flex-end; border-top: 1px solid #eee; padding-top: 10px; } .copy-all-links-btn { background-color: #ff8140; color: white; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer; } .copy-all-links-btn:hover { background-color: #f26d31; } .no-links { color: #999; font-style: italic; } /* 隐藏微博右上角的原生复制按钮 */ .woo-box-flex > div:last-child > div > button.woo-button-main, .woo-box-flex > div:last-child > button.woo-button-main, button.woo-button-main[class*="Copy"], .woo-box-item-flex + button[class*="Button_"] { /* 新版微博右上角按钮 */ display: none !important; } /* 保留格式弹窗样式 */ .format-content-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: white; border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); z-index: 10000; width: 80%; max-height: 80%; padding: 15px; display: none; } .format-modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #eee; } .format-modal-title { font-size: 16px; font-weight: bold; } .format-modal-close { cursor: pointer; font-size: 18px; color: #666; } .format-modal-content { margin-bottom: 10px; max-height: 60vh; overflow-y: auto; white-space: pre-wrap; word-break: break-all; border: 1px solid #eee; padding: 10px; background-color: #f9f9f9; font-family: 'Microsoft YaHei', Arial, sans-serif; line-height: 1.6; } .format-modal-footer { display: flex; justify-content: flex-end; border-top: 1px solid #eee; padding-top: 10px; gap: 10px; } .copy-format-all-btn, .copy-format-plain-btn { background-color: #ff8140; color: white; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer; } .copy-format-all-btn:hover, .copy-format-plain-btn:hover { background-color: #f26d31; } `); // 遮罩层 let modalOverlay = document.createElement('div'); modalOverlay.style.position = 'fixed'; modalOverlay.style.top = '0'; modalOverlay.style.left = '0'; modalOverlay.style.width = '100%'; modalOverlay.style.height = '100%'; modalOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; modalOverlay.style.zIndex = '9999'; modalOverlay.style.display = 'none'; document.body.appendChild(modalOverlay); // 链接弹窗模板 function createLinksModal() { const modal = document.createElement('div'); modal.className = 'links-modal'; modal.innerHTML = ` <div class="links-modal-header"> <div class="links-modal-title">提取的链接</div> <div class="links-modal-close">×</div> </div> <div class="links-modal-content"> <ul class="links-list"></ul> <div class="no-links" style="display: none;">没有找到链接</div> </div> <div class="links-modal-footer"> <button class="copy-all-links-btn">复制所有链接</button> </div> `; document.body.appendChild(modal); // 关闭按钮事件 modal.querySelector('.links-modal-close').addEventListener('click', function() { modal.style.display = 'none'; modalOverlay.style.display = 'none'; }); // 点击遮罩层关闭 modalOverlay.addEventListener('click', function() { modal.style.display = 'none'; modalOverlay.style.display = 'none'; }); // 复制所有链接按钮事件 modal.querySelector('.copy-all-links-btn').addEventListener('click', function() { const links = []; modal.querySelectorAll('.links-list a.real-link').forEach(link => { links.push(link.href); }); if (links.length > 0) { GM_setClipboard(links.join('\n')); this.textContent = '已复制!'; setTimeout(() => { this.textContent = '复制所有链接'; }, 1000); } }); return modal; } // 全局链接弹窗 const linksModal = createLinksModal(); // 格式化内容弹窗模板 function createFormatContentModal() { const modal = document.createElement('div'); modal.className = 'format-content-modal'; modal.innerHTML = ` <div class="format-modal-header"> <div class="format-modal-title">带格式的内容</div> <div class="format-modal-close">×</div> </div> <div class="format-modal-content"></div> <div class="format-modal-footer"> <button class="copy-format-plain-btn">复制纯文本</button> <button class="copy-format-all-btn">复制HTML</button> </div> `; document.body.appendChild(modal); // 关闭按钮事件 modal.querySelector('.format-modal-close').addEventListener('click', function() { modal.style.display = 'none'; modalOverlay.style.display = 'none'; }); // 点击遮罩层关闭 modalOverlay.addEventListener('click', function() { modal.style.display = 'none'; modalOverlay.style.display = 'none'; }); // 复制HTML按钮事件 modal.querySelector('.copy-format-all-btn').addEventListener('click', function() { const content = modal.querySelector('.format-modal-content').innerHTML; const tempTextarea = document.createElement('textarea'); tempTextarea.value = content; document.body.appendChild(tempTextarea); tempTextarea.select(); document.execCommand('copy'); document.body.removeChild(tempTextarea); this.textContent = '已复制!'; setTimeout(() => { this.textContent = '复制HTML'; }, 1000); }); // 复制纯文本按钮事件 modal.querySelector('.copy-format-plain-btn').addEventListener('click', function() { const content = modal.querySelector('.format-modal-content').textContent; // 处理纯文本中的换行,确保保留格式 const formattedText = content .replace(/\n{3,}/g, '\n\n') // 替换多个连续换行为两个换行 .replace(/\s{2,}/g, ' ') // 替换多个连续空格为一个空格 .trim(); // 修剪首尾空白 GM_setClipboard(formattedText); this.textContent = '已复制!'; setTimeout(() => { this.textContent = '复制纯文本'; }, 1000); }); return modal; } // 全局格式化内容弹窗 const formatContentModal = createFormatContentModal(); // 检测是否是短链接 function isShortUrl(url) { // 检查常见的微博短链接域名 const shortUrlDomains = [ 't.cn', 'weibo.cn', 'sinaurl.cn', 'sina.lt', 'dwz.cn' ]; try { const urlObj = new URL(url); return shortUrlDomains.some(domain => urlObj.hostname.includes(domain)) || urlObj.pathname.length < 10; // 路径很短也可能是短链接 } catch (e) { return false; } } // 展开短链接获取真实URL function expandShortUrl(shortUrl, callback) { console.log('微博复制助手: 尝试展开短链接', shortUrl); GM_xmlhttpRequest({ method: 'HEAD', url: shortUrl, timeout: 5000, // 5秒超时 onload: function(response) { // 检查是否有重定向 if (response.finalUrl && response.finalUrl !== shortUrl) { console.log('微博复制助手: 短链接展开成功', shortUrl, '->', response.finalUrl); callback(response.finalUrl); } else { console.log('微博复制助手: 短链接没有重定向', shortUrl); callback(shortUrl); } }, onerror: function(error) { console.error('微博复制助手: 展开短链接失败', shortUrl, error); callback(shortUrl); // 失败时返回原始URL }, ontimeout: function() { console.error('微博复制助手: 展开短链接超时', shortUrl); callback(shortUrl); // 超时时返回原始URL } }); } // 显示链接弹窗 function showLinksModal(links, title = '提取的链接') { const linksList = linksModal.querySelector('.links-list'); const noLinks = linksModal.querySelector('.no-links'); const modalTitle = linksModal.querySelector('.links-modal-title'); // 设置标题 modalTitle.textContent = title; // 清空链接列表 linksList.innerHTML = ''; // 添加链接 if (links && links.length > 0) { links.forEach((link, index) => { const li = document.createElement('li'); const a = document.createElement('a'); a.href = link; a.textContent = link; a.className = 'real-link'; a.target = '_blank'; // 在新标签页打开 li.appendChild(a); // 检查是否是短链接,如果是则尝试展开 if (isShortUrl(link)) { // 添加加载中提示 const loadingSpan = document.createElement('span'); loadingSpan.className = 'link-loading'; loadingSpan.textContent = '正在展开短链接...'; li.appendChild(loadingSpan); // 尝试展开短链接 expandShortUrl(link, function(realUrl) { // 移除加载提示 if (loadingSpan.parentNode) { li.removeChild(loadingSpan); } // 如果展开成功且链接不同,显示原始短链接提示 if (realUrl && realUrl !== link) { a.href = realUrl; // 更新链接的href为真实URL a.textContent = realUrl; // 更新显示的文本为真实URL // 添加原始短链接提示 const originalSpan = document.createElement('span'); originalSpan.className = 'link-original'; originalSpan.textContent = `原始短链接: ${link}`; li.appendChild(originalSpan); } }); } linksList.appendChild(li); }); linksList.style.display = 'block'; noLinks.style.display = 'none'; } else { linksList.style.display = 'none'; noLinks.style.display = 'block'; } // 显示弹窗 linksModal.style.display = 'block'; modalOverlay.style.display = 'block'; } // 显示格式化内容弹窗 function showFormatContentModal(content, title = '带格式的内容') { const modalContent = formatContentModal.querySelector('.format-modal-content'); const modalTitle = formatContentModal.querySelector('.format-modal-title'); // 设置标题 modalTitle.textContent = title; // 设置内容 modalContent.innerHTML = content; // 显示弹窗 formatContentModal.style.display = 'block'; modalOverlay.style.display = 'block'; } // 保留格式提取内容 async function extractContentWithFormat(element) { if (!element) return ''; // 先尝试展开折叠内容 try { console.log('微博复制助手: 尝试展开折叠内容'); await expandContent(element); } catch (error) { console.error('微博复制助手: 展开内容时出错', error); // 继续处理,即使展开失败,也尝试获取已有内容 } // 创建一个虚拟的容器来复制内容结构 const tempContainer = document.createElement('div'); tempContainer.innerHTML = element.innerHTML; // 移除"收起"按钮和文本 const hideButtons = tempContainer.querySelectorAll([ 'a.woo-box-flex[role="button"]', // 新版收起按钮 'button[content="收起"]', // 收起按钮 'a[action-type="fl_fold"]', // 旧版收起按钮 '.WB_text_opt_fold', // 旧版收起区域 '.content_opt_fold' // 收起选项区域 ].join(', ')); hideButtons.forEach(btn => { if (btn && btn.parentNode) { btn.parentNode.removeChild(btn); } }); // 单独处理包含"收起"文本的span元素 const hideSpans = findElementsByText(tempContainer, '收起'); hideSpans.forEach(span => { if (span.tagName.toLowerCase() === 'span' && span.textContent.trim() === '收起') { if (span.parentNode) { span.parentNode.removeChild(span); } } }); // 查找所有包含"收起"文本的元素并移除 const allTextNodes = getAllTextNodes(tempContainer); allTextNodes.forEach(node => { if (node.textContent && node.textContent.includes('收起')) { // 如果只是"收起"两个字,直接删除 if (node.textContent.trim() === '收起') { if (node.parentNode) { node.parentNode.removeChild(node); } } else { // 如果是包含"收起"的文本,替换掉"收起" node.textContent = node.textContent.replace(/收起/g, ''); } } }); // 处理表情包图片,将其转换为文本形式 const emojiImages = tempContainer.querySelectorAll('img.woo-emoji, img[src*="emoticon"], img[src*="emoji"], img[alt*="表情"], img.face'); emojiImages.forEach(img => { // 使用不同的方法尝试提取表情符号文本 let emojiText = ''; // 方法1: 使用alt属性 if (img.alt) { emojiText = img.alt; } // 方法2: 使用title属性 else if (img.title) { emojiText = img.title; } // 方法3: 从src URL中提取表情名称 else if (img.src) { // 从URL中提取表情名称 try { const urlParts = img.src.split('/'); const filename = urlParts[urlParts.length - 1]; // 移除扩展名和查询参数 const nameWithoutExt = filename.split('.')[0].split('?')[0]; // 尝试将驼峰或下划线转换为可读文本 emojiText = nameWithoutExt .replace(/([A-Z])/g, ' $1') // 驼峰转空格 .replace(/_/g, ' ') // 下划线转空格 .trim(); // 如果提取出的文本看起来不像表情名称,使用通用表情描述 if (emojiText.length < 2 || /^\d+$/.test(emojiText)) { emojiText = '[表情]'; } } catch (e) { console.error('微博复制助手: 提取表情名称时出错', e); emojiText = '[表情]'; } } // 如果以上方法都失败,使用通用表示 if (!emojiText) { emojiText = '[表情]'; } // 创建文本节点替换图片 const emojiSpan = document.createElement('span'); emojiSpan.textContent = `[${emojiText}]`; emojiSpan.style.color = '#FF9500'; emojiSpan.style.fontWeight = 'bold'; if (img.parentNode) { img.parentNode.replaceChild(emojiSpan, img); } }); // 处理链接,保留URL const links = tempContainer.querySelectorAll('a[href]'); links.forEach(link => { // 获取链接的href属性和文本 const href = link.getAttribute('href'); const text = link.textContent.trim(); // 如果有有效的URL,在链接文本后添加URL if (href && !href.startsWith('javascript:') && text) { // 检查是否是用户链接(这些不需要额外处理) const isUserLink = link.hasAttribute('usercard') || href.startsWith('/u/') || href.startsWith('/n/'); if (!isUserLink) { // 检查是否是相对链接,需要转换为绝对链接 let fullUrl = href; if (href.startsWith('/')) { // 相对路径转换为绝对路径 fullUrl = window.location.origin + href; } else if (!href.startsWith('http')) { // 如果不是以http开头且不是相对路径,可能是其他格式的链接 fullUrl = 'https://' + href; } // 保持链接的可点击性,但加入URL显示 if (!link.getAttribute('data-processed')) { // 创建一个span来包含原始文本和URL link.setAttribute('data-original-text', text); link.setAttribute('data-url', fullUrl); link.setAttribute('data-processed', 'true'); link.style.color = '#1672F3'; link.style.textDecoration = 'underline'; } } } }); // 移除微博界面元素 const removeElements = tempContainer.querySelectorAll([ '.toolbar_2rnW3', // 工具栏 '.woo-panel-main', // 评论区 '.woo-panel-extra', // 额外面板 '.wbpro-opts', // 操作按钮区域 '.WB_handle', // 旧版操作区 '.wbpro-feed-footer', // 微博底部 '.hide-text' // 隐藏文本(包括"收起") ].join(', ')); removeElements.forEach(el => { if (el.parentNode) { el.parentNode.removeChild(el); } }); // 获取格式化后的HTML const formattedHTML = tempContainer.innerHTML; // 清理HTML - 移除行内的onclick等事件处理器和一些不必要的属性 let cleanHTML = formattedHTML .replace(/\son\w+="[^"]*"/g, '') // 移除所有事件处理器 如onclick .replace(/\sclass="[^"]*"/g, '') // 移除class .replace(/\saction-data="[^"]*"/g, '') // 移除action-data .replace(/\saction-type="[^"]*"/g, '') // 移除action-type .replace(/\snode-type="[^"]*"/g, '') // 移除node-type .replace(/\srenderengine="[^"]*"/g, '') // 移除renderengine .replace(/\scomponent="[^"]*"/g, '') // 移除component .replace(/\smfp="[^"]*"/g, '') // 移除mfp .replace(/\sdata-[^=]*="[^"]*"/g, '') // 移除data-属性 .replace(/\sactived="[^"]*"/g, '') // 移除actived属性 .replace(/\shot="[^"]*"/g, '') // 移除hot属性 .replace(/\sinteractive="[^"]*"/g, '') // 移除interactive属性 .replace(/\srole="[^"]*"/g, ''); // 移除role属性 // 强化处理段落和换行 cleanHTML = cleanHTML .replace(/<div/g, '<p') // div替换为p标签 .replace(/<\/div>/g, '</p>') // 关闭div替换为关闭p .replace(/<br><br>/g, '<br>') // 移除连续的换行 .replace(/<p>\s*<\/p>/g, '') // 移除空段落 .replace(/收起<\/a>/g, '</a>') // 移除"收起"文本 .replace(/收起<\/span>/g, '</span>') // 移除span中的"收起"文本 .replace(/"收起"/g, '""'); // 移除属性中的"收起"文本 return cleanHTML; } // 查找所有文本节点的辅助函数 function getAllTextNodes(element) { const walker = document.createTreeWalker( element, NodeFilter.SHOW_TEXT, null, false ); const nodes = []; let node; while (node = walker.nextNode()) { nodes.push(node); } return nodes; } // 通过文本内容查找元素的辅助函数 function findElementsByText(parentElement, searchText) { if (!parentElement) return []; const result = []; const allElements = parentElement.querySelectorAll('*'); allElements.forEach(element => { if (element.textContent && element.textContent.includes(searchText)) { result.push(element); } }); return result; } // 从HTML内容中提取链接 function extractLinksFromElement(element) { if (!element) return []; const links = []; const tempContainer = document.createElement('div'); tempContainer.innerHTML = element.innerHTML; // 查找所有链接 const anchors = tempContainer.querySelectorAll('a[href]'); anchors.forEach(anchor => { const href = anchor.getAttribute('href'); if (href && !href.startsWith('javascript:')) { // 排除用户链接 const isUserLink = anchor.hasAttribute('usercard') || href.startsWith('/u/') || href.startsWith('/n/'); if (!isUserLink) { // 处理相对链接 let fullUrl = href; if (href.startsWith('/')) { fullUrl = window.location.origin + href; } else if (!href.startsWith('http')) { fullUrl = 'https://' + href; } links.push(fullUrl); } } }); return links; } // 检测页面变化的观察器 const observer = new MutationObserver(checkForNewContent); // 启动监听 function startObserver() { observer.observe(document.body, { childList: true, subtree: true }); } // 检查新的微博内容和评论 function checkForNewContent(mutations) { // 处理微博内容 - 适配各种版本 const newPosts = document.querySelectorAll([ 'article:not([data-processed])', '.Feed_body_3R4ey:not([data-processed])', '.wbpro-feed-content:not([data-processed])', '.WB_detail:not([data-processed])', '.wbpro-feed-item:not([data-processed])', '.wbpro-scroller:not([data-processed])' ].join(', ')); newPosts.forEach(post => { addCopyButtonToPost(post); post.setAttribute('data-processed', 'true'); }); // 处理评论内容 - 适配各种版本 const newComments = document.querySelectorAll([ '.Comment_content_2jJd2:not([data-processed])', '.wbpro-commentlist-item:not([data-processed])', '.list_li:not([data-processed])', '.woo-panel-commentMain:not([data-processed])', '.woo-box-item-inlineBlock:not([data-processed])', '.wbpro-list .item1:not([data-processed])', '.item1 .con1:not([data-processed])', '.con1:not([data-processed])', '.info:not([data-processed])', // 添加二级和三级评论选择器 '.list2 .item2:not([data-processed])', '.list2 .con2:not([data-processed])', '.con2:not([data-processed])' ].join(', ')); newComments.forEach(comment => { addCopyButtonToComment(comment); comment.setAttribute('data-processed', 'true'); }); // 专门处理二级评论 processSecondLevelComments(); // 监听评论图标点击 listenToCommentIcons(); // 隐藏原生复制按钮 hideNativeCopyButtons(); } // 专门处理二级评论 function processSecondLevelComments() { // 查找所有二级评论容器 const secondLevelContainers = document.querySelectorAll('.list2:not([data-processed])'); secondLevelContainers.forEach(container => { // 标记容器为已处理 container.setAttribute('data-processed', 'true'); // 查找所有二级评论项 const secondLevelItems = container.querySelectorAll('.item2:not([data-processed]), .con2:not([data-processed])'); secondLevelItems.forEach(item => { addCopyButtonToComment(item); item.setAttribute('data-processed', 'true'); }); }); } // 监听评论图标点击 function listenToCommentIcons() { // 找到所有未处理的评论图标 const commentIcons = document.querySelectorAll([ '.woo-font--comment:not([data-processed])', '.toolbar_iconWrap_3-rI7:not([data-processed])', '.wbpro-opts-item[title="评论"]:not([data-processed])', '.WB_handle li[title="评论"]:not([data-processed])', '.opt i.woo-font--comment:not([data-processed])' ].join(', ')); commentIcons.forEach(icon => { // 标记为已处理 icon.setAttribute('data-processed', 'true'); // 添加点击事件监听器 icon.addEventListener('click', function() { // 当评论图标被点击后,评论区通常会在几百毫秒内出现 // 给予足够的时间让评论区加载出来 setTimeout(() => { processNewlyLoadedComments(); }, 500); // 再次检查,以防评论加载较慢 setTimeout(() => { processNewlyLoadedComments(); }, 1000); // 最后一次检查 setTimeout(() => { processNewlyLoadedComments(); }, 2000); }); console.log('微博复制助手: 为评论图标添加监听器', icon); }); } // 处理新加载的评论区域 function processNewlyLoadedComments() { // 评论区域的可能选择器 const commentAreaSelectors = [ '.woo-panel-main', // 新版微博评论区 '.woo-box-wrap', // 新版微博评论容器 '.wbpro-commentlist', // 新版微博专业版评论列表 '.WB_feed_expand', // 旧版微博评论区 '.list_box', // 旧版移动版评论列表 '.comment-content', // 通用评论内容 '.wbpro-list', // 新版微博评论列表 '.Feed_box_3fswx', // 新版微博评论框容器 '.item1', // 评论项 '.con1', // 评论内容 '.list2' // 二级评论容器 ]; // 尝试检查所有可能的评论区域 commentAreaSelectors.forEach(selector => { const commentAreas = document.querySelectorAll(selector); commentAreas.forEach(area => { // 为评论区域内的评论添加复制按钮 const comments = area.querySelectorAll([ '.Comment_content_2jJd2:not([data-processed])', '.wbpro-commentlist-item:not([data-processed])', '.list_li:not([data-processed])', '.woo-panel-commentMain:not([data-processed])', '.woo-box-item-inlineBlock:not([data-processed])', '.wbpro-list .item1:not([data-processed])', '.item1 .con1:not([data-processed])', '.con1:not([data-processed])', '.info:not([data-processed])', // 添加二级评论选择器 '.list2 .item2:not([data-processed])', '.list2 .con2:not([data-processed])', '.con2:not([data-processed])' ].join(', ')); if (comments.length > 0) { console.log('微博复制助手: 找到新加载的评论', comments.length); comments.forEach(comment => { addCopyButtonToComment(comment); comment.setAttribute('data-processed', 'true'); }); } // 进一步检查评论区域中是否有直接的评论项 if (area.classList.contains('item1') || area.classList.contains('con1') || area.classList.contains('item2') || area.classList.contains('con2')) { if (!area.getAttribute('data-processed')) { addCopyButtonToComment(area); area.setAttribute('data-processed', 'true'); } } // 专门处理二级评论区域 if (area.classList.contains('list2') && !area.getAttribute('data-processed')) { processSecondLevelComments(); area.setAttribute('data-processed', 'true'); } }); }); // 直接查找所有可能的评论项 const directComments = document.querySelectorAll([ '.wbpro-list .item1:not([data-processed])', '.item1 .con1:not([data-processed])', // 二级评论 '.list2 .item2:not([data-processed])', '.list2 .con2:not([data-processed])' ].join(', ')); if (directComments.length > 0) { console.log('微博复制助手: 找到直接评论项', directComments.length); directComments.forEach(comment => { addCopyButtonToComment(comment); comment.setAttribute('data-processed', 'true'); }); } } // 隐藏微博原生的复制按钮 function hideNativeCopyButtons() { // 使用JavaScript进一步隐藏可能的复制按钮 const possibleSelectors = [ // 右上角区域 '.woo-box-flex > div > button.woo-button-main', '.woo-box-flex > div:last-child > button', // 右上角具有特定类名的按钮 'button.woo-button-main[class*="Copy"]' ]; possibleSelectors.forEach(selector => { const elements = document.querySelectorAll(selector); elements.forEach(el => { // 检查文本内容是否包含"复制博文" if (el.textContent && el.textContent.includes('复制博文')) { el.style.display = 'none'; } }); }); } // 检查并展开折叠内容 async function expandContent(element) { if (!element) return false; // 可能的展开按钮选择器(不使用:contains伪类,它不是标准CSS) const expandSelectors = [ // 新版微博 'button.woo-button-flat.woo-button-s[class*="ContentMore"]', 'button.woo-button-flat.woo-button-s.ContentMore', 'button[class*="expand"]', 'button[class*="Expand"]', 'div[class*="expand"]', 'a[class*="expand"]', // 旧版微博 'a[action-type="fl_unfold"]', '.WB_text_opt', '.WB_text a[onclick*="unfold"]', // 特殊的span展开按钮 'span.expand', '.expand' ]; // 搜索展开按钮 let expandButton = null; // 首先尝试使用标准选择器 for (const selector of expandSelectors) { try { expandButton = element.querySelector(selector) || element.parentNode.querySelector(selector); if (expandButton) { console.log('微博复制助手: 找到展开按钮(选择器)', selector, expandButton); break; } } catch (error) { console.error('微博复制助手: 选择器错误', selector, error); } } // 如果没找到,尝试通过文本内容查找 if (!expandButton) { // 查找包含"展开"或"展开全文"的元素 const textButtons = [ ...findElementsByText(element, '展开'), ...findElementsByText(element, '展开全文') ]; // 筛选可能的按钮元素,包括span元素 const possibleButtons = textButtons.filter(el => { const tagName = el.tagName.toLowerCase(); return tagName === 'button' || tagName === 'a' || tagName === 'span' || el.role === 'button' || el.getAttribute('role') === 'button' || el.classList.contains('expand') || // 特别处理展开类 el.onclick || el.getAttribute('onclick'); }); if (possibleButtons.length > 0) { expandButton = possibleButtons[0]; console.log('微博复制助手: 找到展开按钮(文本)', expandButton); } } // 如果找到展开按钮,点击它 if (expandButton) { console.log('微博复制助手: 准备点击展开按钮', expandButton); try { // 对于span.expand元素尝试多种点击方式 if (expandButton.tagName.toLowerCase() === 'span' && expandButton.classList.contains('expand')) { console.log('微博复制助手: 检测到span.expand元素,尝试特殊处理'); // 方法1: 直接点击 expandButton.click(); // 方法2: 创建点击事件 const clickEvent = document.createEvent('MouseEvents'); clickEvent.initEvent('click', true, true); expandButton.dispatchEvent(clickEvent); // 方法3: 尝试点击父元素 if (expandButton.parentElement) { console.log('微博复制助手: 尝试点击父元素'); expandButton.parentElement.click(); } // 方法4: 尝试移除展开元素并修改父元素样式以显示全部内容 const parentEl = expandButton.parentElement; if (parentEl && parentEl.classList.contains('detail_wbtext_4CRf9')) { console.log('微博复制助手: 尝试移除展开元素并显示全部内容'); // 保存原始内容但移除展开按钮 const fullText = parentEl.innerHTML.replace(/<span class="expand">展开<\/span>/, ''); parentEl.innerHTML = fullText; // 移除可能的最大高度限制 parentEl.style.maxHeight = 'none'; parentEl.style.overflow = 'visible'; } } else { // 正常点击 expandButton.click(); } // 等待内容展开 await new Promise(resolve => setTimeout(resolve, 800)); // 增加等待时间,确保内容展开 console.log('微博复制助手: 内容已展开'); return true; } catch (error) { console.error('微博复制助手: 展开内容时出错', error); } } return false; } // 获取和处理内容,保留链接 async function extractContentWithLinks(element) { if (!element) return ''; // 先尝试展开折叠内容 try { console.log('微博复制助手: 尝试展开折叠内容'); await expandContent(element); } catch (error) { console.error('微博复制助手: 展开内容时出错', error); // 继续处理,即使展开失败,也尝试获取已有内容 } // 创建一个虚拟的容器来复制内容结构 const tempContainer = document.createElement('div'); tempContainer.innerHTML = element.innerHTML; // 检查是否是评论内容 const isComment = element.closest('.item1, .item2, .con1, .con2') || element.classList.contains('item1') || element.classList.contains('item2') || element.classList.contains('con1') || element.classList.contains('con2'); // 处理表情包图片,将其转换为文本形式 const emojiImages = tempContainer.querySelectorAll('img.woo-emoji, img[src*="emoticon"], img[src*="emoji"], img[alt*="表情"], img.face'); emojiImages.forEach(img => { // 使用不同的方法尝试提取表情符号文本 let emojiText = ''; // 方法1: 使用alt属性 if (img.alt) { emojiText = img.alt; } // 方法2: 使用title属性 else if (img.title) { emojiText = img.title; } // 方法3: 从src URL中提取表情名称 else if (img.src) { // 从URL中提取表情名称 try { const urlParts = img.src.split('/'); const filename = urlParts[urlParts.length - 1]; // 移除扩展名和查询参数 const nameWithoutExt = filename.split('.')[0].split('?')[0]; // 尝试将驼峰或下划线转换为可读文本 emojiText = nameWithoutExt .replace(/([A-Z])/g, ' $1') // 驼峰转空格 .replace(/_/g, ' ') // 下划线转空格 .trim(); // 如果提取出的文本看起来不像表情名称,使用通用表情描述 if (emojiText.length < 2 || /^\d+$/.test(emojiText)) { emojiText = '[表情]'; } } catch (e) { console.error('微博复制助手: 提取表情名称时出错', e); emojiText = '[表情]'; } } // 如果以上方法都失败,使用通用表示 if (!emojiText) { emojiText = '[表情]'; } // 创建文本节点替换图片 const emojiTextNode = document.createTextNode(`[${emojiText}]`); if (img.parentNode) { img.parentNode.replaceChild(emojiTextNode, img); } }); // 处理链接,保留URL const links = tempContainer.querySelectorAll('a[href]'); links.forEach(link => { // 获取链接的href属性和文本 const href = link.getAttribute('href'); const text = link.textContent.trim(); // 如果有有效的URL,在链接文本后添加URL if (href && !href.startsWith('javascript:') && text) { // 检查是否是用户链接(这些不需要保留) const isUserLink = link.hasAttribute('usercard') || href.startsWith('/u/') || href.startsWith('/n/'); if (!isUserLink) { // 检查是否是相对链接,需要转换为绝对链接 let fullUrl = href; if (href.startsWith('/')) { // 相对路径转换为绝对路径 fullUrl = window.location.origin + href; } else if (!href.startsWith('http')) { // 如果不是以http开头且不是相对路径,可能是其他格式的链接 fullUrl = 'https://' + href; } // 替换链接元素为文本加URL link.textContent = `${text} [${fullUrl}]`; } } }); // 获取处理后的文本内容 let content = tempContainer.innerText || tempContainer.textContent; if (isComment) { // 简化处理:找到第一个冒号(中英文都考虑),移除前面的所有内容 const colonIndex = Math.max(content.indexOf(':'), content.indexOf(':')); if (colonIndex > -1) { content = content.substring(colonIndex + 1).trim(); } // 不再处理回复评论的情况,保持一致的处理方式 } // 清理内容 content = content.replace(/复制博文|复制评论|收起|转发|评论|赞|展开全文|投诉|展开|查看图片/g, '').trim(); // 移除Unicode控制字符和特殊空白 content = content.replace(/[\u0000-\u001F\u007F-\u009F\u200B-\u200F\u2028-\u202F\u2060-\u2069]/g, ''); // 移除多余空格和换行,但保留基本格式 content = content.replace(/\s{3,}/g, '\n').trim(); return content; } // 向微博内容添加复制按钮 function addCopyButtonToPost(post) { // 适配各种版本的微博内容选择器 const contentSelectors = [ '.detail_wbtext_4CRf9', // 放在最前面,优先查找 '.Feed_body_3R4ey', '.wbpro-feed-content-text', '.WB_text', '.woo-box-item-flex', '.text' ]; // 尝试每个选择器直到找到一个匹配 let contentElement = null; for (const selector of contentSelectors) { contentElement = post.querySelector(selector); if (contentElement) break; } if (!contentElement) return; // 检查是否已经添加了复制按钮 if (post.querySelector('.copy-btn')) return; // 创建复制按钮 const copyBtn = document.createElement('button'); copyBtn.className = 'copy-btn'; // 使用自定义的类 copyBtn.textContent = '复制博文'; copyBtn.style.zIndex = '9999'; // 确保按钮在最上层 copyBtn.style.marginTop = '5px'; // 与内容保持一些距离 copyBtn.style.marginBottom = '5px'; // 底部也加点间距 // 创建提取链接按钮 const extractLinksBtn = document.createElement('button'); extractLinksBtn.className = 'extract-links-btn'; extractLinksBtn.textContent = '提取链接'; extractLinksBtn.style.zIndex = '9999'; extractLinksBtn.style.marginTop = '5px'; extractLinksBtn.style.marginBottom = '5px'; // 创建复制带格式的按钮 const copyFormatBtn = document.createElement('button'); copyFormatBtn.className = 'copy-format-btn'; copyFormatBtn.textContent = '复制带格式'; copyFormatBtn.style.zIndex = '9999'; copyFormatBtn.style.marginTop = '5px'; copyFormatBtn.style.marginBottom = '5px'; // 添加复制按钮事件 copyBtn.addEventListener('click', async function(e) { e.preventDefault(); e.stopPropagation(); // 保存按钮原始文本 const originalText = copyBtn.textContent; try { // 先提取内容,然后再更改按钮状态 const content = await extractContentWithLinks(contentElement); // 内容提取完成后再更改按钮状态 copyBtn.textContent = '处理中...'; copyBtn.disabled = true; // 复制到剪贴板 GM_setClipboard(content); // 提示用户 copyBtn.textContent = '已复制!'; copyBtn.disabled = false; setTimeout(() => { copyBtn.textContent = originalText; }, 1000); } catch (error) { console.error('微博复制助手: 复制时出错', error); copyBtn.textContent = '复制失败'; copyBtn.disabled = false; setTimeout(() => { copyBtn.textContent = originalText; }, 1000); } }); // 添加提取链接按钮事件 extractLinksBtn.addEventListener('click', async function(e) { e.preventDefault(); e.stopPropagation(); try { // 先尝试展开内容 try { await expandContent(contentElement); } catch (error) { console.error('微博复制助手: 展开内容时出错', error); } // 提取链接 const links = extractLinksFromElement(contentElement); // 如果没有链接,直接显示提示 if (!links || links.length === 0) { alert('未找到任何链接'); return; } // 显示链接弹窗 showLinksModal(links, '博文中的链接'); } catch (error) { console.error('微博复制助手: 提取链接时出错', error); alert('提取链接失败'); } }); // 添加复制带格式按钮事件 copyFormatBtn.addEventListener('click', async function(e) { e.preventDefault(); e.stopPropagation(); // 保存按钮原始文本 const originalText = copyFormatBtn.textContent; try { // 更改按钮状态 copyFormatBtn.textContent = '处理中...'; copyFormatBtn.disabled = true; // 提取带格式的内容 const formattedContent = await extractContentWithFormat(contentElement); // 显示格式化内容弹窗 showFormatContentModal(formattedContent, '博文带格式内容'); // 恢复按钮状态 copyFormatBtn.textContent = originalText; copyFormatBtn.disabled = false; } catch (error) { console.error('微博复制助手: 复制带格式时出错', error); copyFormatBtn.textContent = '处理失败'; copyFormatBtn.disabled = false; setTimeout(() => { copyFormatBtn.textContent = originalText; }, 1000); } }); // 优先检查是否存在detail_wbtext_4CRf9类元素 const detailWbTextElement = post.querySelector('.detail_wbtext_4CRf9') || (contentElement.classList.contains('detail_wbtext_4CRf9') ? contentElement : null); if (detailWbTextElement) { // 如果找到了detail_wbtext_4CRf9元素,将按钮放在它的后面 if (detailWbTextElement.parentNode) { detailWbTextElement.parentNode.insertBefore(copyFormatBtn, detailWbTextElement.nextSibling); detailWbTextElement.parentNode.insertBefore(copyBtn, copyFormatBtn); detailWbTextElement.parentNode.insertBefore(extractLinksBtn, detailWbTextElement.nextSibling.nextSibling.nextSibling); console.log('微博复制助手: 添加按钮到detail_wbtext_4CRf9后'); return; } } // 如果没有找到detail_wbtext_4CRf9元素,回退到之前的查找逻辑 // 查找微博内容的主容器 let feedContentContainer = null; // 首先检查post本身是否有wbpro-feed-content类 if (post.classList.contains('wbpro-feed-content')) { feedContentContainer = post; } else { // 查找post内的wbpro-feed-content元素 feedContentContainer = post.querySelector('.wbpro-feed-content'); // 如果找不到,尝试向上查找祖先元素 if (!feedContentContainer) { let parent = post.parentElement; while (parent && parent !== document.body) { if (parent.classList.contains('wbpro-feed-content')) { feedContentContainer = parent; break; } parent = parent.parentElement; } } } // 如果找到了wbpro-feed-content容器,将按钮添加到其中 if (feedContentContainer) { // 如果已经有操作区域(底部工具栏),将按钮放在那里 const toolbarArea = feedContentContainer.querySelector('.wbpro-opts') || feedContentContainer.querySelector('.toolbar_2rnW3') || feedContentContainer.querySelector('.WB_handle'); if (toolbarArea) { // 添加到工具栏开头 if (toolbarArea.firstChild) { toolbarArea.insertBefore(copyFormatBtn, toolbarArea.firstChild); toolbarArea.insertBefore(copyBtn, copyFormatBtn); toolbarArea.insertBefore(extractLinksBtn, toolbarArea.firstChild.nextSibling.nextSibling); } else { toolbarArea.appendChild(copyBtn); toolbarArea.appendChild(copyFormatBtn); toolbarArea.appendChild(extractLinksBtn); } console.log('微博复制助手: 添加按钮到工具栏'); } else { // 没有工具栏,添加到内容元素后面 feedContentContainer.appendChild(copyBtn); feedContentContainer.appendChild(copyFormatBtn); feedContentContainer.appendChild(extractLinksBtn); console.log('微博复制助手: 添加按钮到微博内容容器'); } return; } // 如果找不到wbpro-feed-content,使用内容元素作为放置位置 const targetElement = contentElement.parentNode; if (targetElement) { targetElement.insertBefore(copyFormatBtn, contentElement.nextSibling); targetElement.insertBefore(copyBtn, copyFormatBtn); targetElement.insertBefore(extractLinksBtn, contentElement.nextSibling.nextSibling.nextSibling); console.log('微博复制助手: 添加按钮到内容元素后'); } } // 向评论添加复制按钮 function addCopyButtonToComment(comment) { // 适配各种版本的评论内容选择器 const contentSelectors = [ '.Comment_text_1Lcol', '.wbpro-commentlist-content', '.WB_text', '.detail_wbtext_4CRf9', '.text-content', '.text', '.con1 .text' ]; // 尝试每个选择器直到找到一个匹配 let contentElement = null; for (const selector of contentSelectors) { contentElement = comment.querySelector(selector); if (contentElement) break; } // 如果没找到明确的内容元素,但评论是con1类,可以直接使用 if (!contentElement && (comment.classList.contains('con1') || comment.classList.contains('woo-box-item-flex'))) { contentElement = comment; } if (!contentElement) return; // 检查是否已经添加了复制按钮 if (comment.querySelector('.copy-btn')) return; // 创建复制按钮 const copyBtn = document.createElement('button'); copyBtn.className = 'copy-btn'; copyBtn.textContent = '复制评论'; copyBtn.style.marginLeft = '5px'; copyBtn.style.zIndex = '9999'; // 确保按钮在最上层 // 创建提取链接按钮 const extractLinksBtn = document.createElement('button'); extractLinksBtn.className = 'extract-links-btn'; extractLinksBtn.textContent = '提取链接'; extractLinksBtn.style.marginLeft = '5px'; extractLinksBtn.style.zIndex = '9999'; // 添加复制按钮事件 copyBtn.addEventListener('click', async function(e) { e.preventDefault(); e.stopPropagation(); // 保存按钮原始文本 const originalText = copyBtn.textContent; try { // 先提取内容,然后再更改按钮状态 const content = await extractContentWithLinks(contentElement); // 内容提取完成后再更改按钮状态 copyBtn.textContent = '处理中...'; copyBtn.disabled = true; // 复制到剪贴板 GM_setClipboard(content); // 提示用户 copyBtn.textContent = '已复制!'; copyBtn.disabled = false; setTimeout(() => { copyBtn.textContent = originalText; }, 1000); } catch (error) { console.error('微博复制助手: 复制评论时出错', error); copyBtn.textContent = '复制失败'; copyBtn.disabled = false; setTimeout(() => { copyBtn.textContent = originalText; }, 1000); } }); // 添加提取链接按钮事件 extractLinksBtn.addEventListener('click', async function(e) { e.preventDefault(); e.stopPropagation(); try { // 提取链接 const links = extractLinksFromElement(contentElement); // 如果没有链接,直接显示提示 if (!links || links.length === 0) { alert('未找到任何链接'); return; } // 显示链接弹窗 showLinksModal(links, '评论中的链接'); } catch (error) { console.error('微博复制助手: 提取评论链接时出错', error); alert('提取链接失败'); } }); // 优先寻找评论区信息行元素 const infoSelectors = [ '.info', '.info .opt', '.opt', '.toolbar_2rnW3', '.Comment_handle_2W1IA', '.WB_handle' ]; // 查找合适的位置 let targetElement = null; // 首先尝试找info元素 for (const selector of infoSelectors) { targetElement = comment.querySelector(selector); if (targetElement) break; } // 如果找到了元素,确保不是item1in或woo-box-flex if (targetElement && !targetElement.classList.contains('item1in') && !targetElement.classList.contains('woo-box-flex')) { // 添加按钮到找到的元素中 targetElement.appendChild(copyBtn); targetElement.appendChild(extractLinksBtn); } else { // 如果没找到合适的位置,或者找到的位置是不希望添加的类, // 尝试找con1元素 const con1Element = comment.querySelector('.con1'); if (con1Element && !con1Element.classList.contains('woo-box-flex')) { con1Element.appendChild(copyBtn); con1Element.appendChild(extractLinksBtn); } else if (contentElement.parentNode && !contentElement.parentNode.classList.contains('item1in') && !contentElement.parentNode.classList.contains('woo-box-flex')) { // 如果con1也不合适,尝试添加到内容元素的父节点 contentElement.parentNode.appendChild(copyBtn); contentElement.parentNode.appendChild(extractLinksBtn); } else { // 如果以上都不适合,找评论元素本身,但确保不是item1in或woo-box-flex if (!comment.classList.contains('item1in') && !comment.classList.contains('woo-box-flex')) { comment.appendChild(copyBtn); comment.appendChild(extractLinksBtn); } else { // 最后尝试在con1内找一个适合的子元素 const textElement = comment.querySelector('.text'); if (textElement) { textElement.appendChild(copyBtn); textElement.appendChild(extractLinksBtn); } // 如果以上都失败,则不添加按钮 } } } // 打印调试信息 console.log('微博复制助手: 添加复制和提取链接按钮到评论'); } // 检查页面是否有新加载的内容 function periodicCheck() { checkForNewContent(); hideNativeCopyButtons(); // 定期检查并隐藏新出现的原生按钮 // 每2秒检查一次新内容 setTimeout(periodicCheck, 2000); } // 初始化 function init() { console.log('微博复制助手: 初始化脚本'); // 首次检查页面 setTimeout(() => { checkForNewContent(); startObserver(); periodicCheck(); hideNativeCopyButtons(); }, 2000); // 给页面加载足够的时间 // 监听页面滚动事件,处理懒加载内容 window.addEventListener('scroll', function() { setTimeout(() => { checkForNewContent(); hideNativeCopyButtons(); }, 500); }); } // 启动脚本 window.addEventListener('load', init); // 如果页面已经加载完成,立即初始化 if (document.readyState === 'complete' || document.readyState === 'interactive') { init(); } })();