// ==UserScript== // @name 三合一:自动展开 + 滚动记忆 + 回顶/刷新/DeepSeek // @namespace https://github.com/merged-scripts // @version 2.3.0 // @description 自动展开隐藏内容(常用网站)、记录滚动位置并恢复、回顶/刷新/DeepSeek按钮(半透明靠右,Via优化) // @author 耗子Sky & airbash (整合优化) // @match *://*/* // @run-at document-start // @grant none // @license GPL-3.0 // ==/UserScript== (function() { 'use strict'; // ==================== 用户配置区 ==================== const CONFIG = { ENABLE_DEEPSEEK_BTN: true, // 是否显示 DeepSeek 跳转按钮 DEEPSEEK_URL: 'https://chat.deepseek.com/', // DeepSeek 目标地址 SCROLL_THRESHOLD: 200, // 滚动超过此值显示浮动按钮 }; // ==================== 公共工具函数 ==================== const now = Date.now || function() { return new Date().getTime(); }; // 节流函数 function throttle(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function() { var _now = now(); if (!previous && options.leading === false) previous = _now; var remaining = wait - (_now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = _now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; } // 向页面注入样式(带 ID 去重) function addStyleOnce(css, id) { if (id && document.getElementById(id)) return; const style = document.createElement('style'); if (id) style.id = id; style.textContent = css; document.head.appendChild(style); return style; } // 各功能专用样式注入(避免冲突) const addStyle_Scroll = (css) => addStyleOnce(css, 'scroll-memory-style'); const addStyle_AutoUnfold = (css) => addStyleOnce(css, 'auto-unfold-style'); const addStyle_Buttons = (css) => addStyleOnce(css, 'buttons-style'); // 条件等待执行(轮询直到条件满足或超时) function runNeed(condition, fn, option = { count: 20, delay: 200, failFn: () => {} }, ...args) { if (typeof condition !== 'function' || typeof fn !== 'function') return; let ok = false; let count = 0; const maxCount = option.count || 20; const delay = option.delay || 200; const failFn = option.failFn || (() => {}); function check() { if (condition.call(this, count + 1)) { fn.apply(this, args); } else if (++count < maxCount) { setTimeout(check, delay); } else { failFn(); } } check(); } // ==================== 功能1:自动展开隐藏内容(前置,精简优化) ==================== (function() { // ---------- 站点规则定义 ---------- // 精简后保留常用网站,合并相似规则,保证核心功能 const websites = [ { name: 'CSDN', match: /blog\.csdn\.net|ask\.csdn\.net|download\.csdn\.net|wenku\.csdn\.net/, handles: [ { type: 'click', selector: '.hide-preCode-bt' }, { type: 'display', selector: '.hide-article-box, .btn_mod, .readall_box, .expandBtn, .fl, .unfold-font, .el-button--text, .text-all' }, { type: 'height', selector: '.article_content, .normal-style' }, { type: 'click', selector: '.btn_comment_readmore, .ic_ask_down_reeow' } ] }, { name: '简书', match: /jianshu\.com\/p/, handles: [ { type: 'display', selector: '.collapse-tips' }, { type: 'height', selector: '.collapse-free-content' }, { type: 'overflow', selector: 'body' } ], custom: () => { addStyle_AutoUnfold('.collapse-free-content::after { height: 0 !important; }'); } }, { name: '知乎', match: /zhihu\.com\/question/, handles: [ { type: 'display', selector: '.ContentItem-rightButton' }, { type: 'height', selector: '.RichContent-inner--collapsed' } ], custom: () => { addStyle_AutoUnfold(` .RichContent--unescapable.is-collapsed .RichContent-inner { mask-image: unset !important; } .RichContent.is-collapsed { cursor: unset !important; } `); } }, { name: '百度知道/经验/百科/贴吧/文库/百家号', match: /(zhidao|jingyan|baike|tieba|wk|tanbi|easylearn|baijiahao|mbd)\.baidu\.com/, handles: [ { type: 'display', selector: '.read-whole-mask, .w-detail-display-btn, .wgt-best-mask, .wgt-answers-mask, #show-hide-container, .show-answer-dispute, .replace_tip, .blur-bg, .continue-read-wrap, .shiti-answer .mask, .oPadding, .foldMaskWrapper, [class^=foldMaskWrapper-]' }, { type: 'height', selector: '.exp-content-container, .w-detail-container, .best-text, .answer-text, .replace_div, .reader-copy, .shiti-answer .analysis-text, .question-cont .tigan, .mainContent, #mainContentContainer, #dynamicItem' }, { type: 'click', selector: '.more-img-opt, .layout-icons_down-arrow, .j_lzl_m' }, { type: 'classList', selector: '.answer', remove: 'answer-hide answer-dispute-hide' } ], custom: () => { // 百度百科加载更多 const btn = document.querySelector('.yx-load-more-inner'); if (btn) btn.dispatchEvent(new Event('tap')); // 百度文库边距修复 const foldPager = document.querySelector('.fold-pager'); if (foldPager) foldPager.style.marginTop = '0'; const readView = document.querySelector('#read-view'); if (readView) readView.setAttribute('scrolling', 'yes'); } }, { name: '新浪/网易/搜狐/腾讯/凤凰/澎湃新闻', match: /(sina\.cn|3g\.163\.com|sohu\.com\/a|view\.inews\.qq\.com|ifeng\.com|thepaper\.cn)/, handles: [ { type: 'display', selector: '.look_more, .show_article, .lookall-box, [class^=show-more_outer__], [class^=show-more-article_cover__], [class^=index_more_], [class^=index_tip_], .showall, #clickForMore' }, { type: 'height', selector: '.s_card, article, #mp-editor, [class^=show-more_height-not-full__], [class^=index_main_content_], .article, .newsdetail_body' } ], custom: () => { // 搜狐特殊处理 const artLookAll = document.querySelector('#artLookAll'); if (artLookAll) artLookAll.click(); } }, { name: '今日头条/东方资讯/丁香园', match: /(toutiao\.com\/article|mini\.eastday\.com|dxy\.cn)/, handles: [ { type: 'display', selector: '.toggle-button-container, .content-shadow, .Unfolded-btn, [class^=contentWrapBottom___], .show-all' }, { type: 'height', selector: '.content, .article-content, .dicussion-text, [class^=contentWrap___], .article__content' } ] }, { name: 'B站专栏/动态', match: /bilibili\.com\/(opus|space)/, handles: [ { type: 'display', selector: '.opus-read-more' }, { type: 'classList', selector: '.opus-module-content', remove: 'limit show-read-text' } ], custom: () => { document.querySelectorAll('.folded').forEach(el => el.className = 'bili-rich-text__content'); document.querySelectorAll('.bili-rich-text__action').forEach(el => el.innerText = '收起'); } }, { name: '微博/豆瓣', match: /weibo\.com\/ttarticle|douban\.com/, handles: [ { type: 'height', selector: '.WB_editor_iframe_new, .note-content' }, { type: 'display', selector: '.btn_line, .oia-readall' } ], custom: () => { // 豆瓣简介展开 const introP = document.querySelector('.subject-intro p'); if (introP) { const data = introP.getAttribute('data-content'); if (data) introP.innerText = data; } // 豆瓣评论展开 document.querySelectorAll('.LinesEllipsis-readmore, .expand, .a_show_full, .fold-switch') .forEach(el => el.click()); } }, { name: '云开发者社区(阿里/腾讯/华为)', match: /developer\.aliyun\.com|cloud\.tencent\.com|huaweicloud\.csdn\.net/, handles: [ { type: 'height', selector: '.article-hide-content, .com-markdown-collpase-main, .cdc-expand-area__main, .user-article' }, { type: 'display', selector: '.article-hide-box, .com-markdown-collpase-toggle, .cdc-expand-area__toggle, .article-show-more' } ] }, { name: '汽车之家/太平洋电脑/中关村在线/游民星空', match: /autohome\.com\.cn|pconline\.com\.cn|zol\.com\.cn|gamersky\.com/, handles: [ { type: 'display', selector: '#continue_reading, #continue_reading_new, .show_article, .unfold-article-btn, .gsAreaContextOpen' }, { type: 'height', selector: '#topicContentSection, .art-content, .article-content, #gsAreaContext' }, { type: 'classList', selector: '.fn-hide', remove: 'fn-hide' } ] }, { name: '东方财富/喜马拉雅/代码随想录', match: /eastmoney\.com|m\.ximalaya\.com|programmercarl\.com/, handles: [ { type: 'display', selector: '.fold-btn, .fold-arrow, .fold-mask, .my_ad_wrap, #foldup_box, #read-more-wrap' }, { type: 'height', selector: '#articleContent, #text-content, #content, .stretch-box, #container' } ], custom: () => { // 喜马拉雅移除展开按钮 const stretchMore = document.querySelector('.stretch-more'); if (stretchMore?.parentElement) stretchMore.parentElement.remove(); } } ]; // ---------- 核心展开逻辑 ---------- let unfoldTimer = null; let unfoldAttempts = 0; const MAX_ATTEMPTS = 30; // 执行单次展开 function performUnfold() { const url = location.href; for (const site of websites) { if (!site.match.test(url)) continue; // 执行自定义函数 if (site.custom) { try { site.custom(); } catch(e) {} } if (!site.handles) continue; for (const handle of site.handles) { const elements = document.querySelectorAll(handle.selector); elements.forEach(el => { switch (handle.type) { case 'display': el.style.display = 'none'; break; case 'height': el.style.setProperty('height', 'auto', 'important'); el.style.setProperty('max-height', 'none', 'important'); break; case 'overflow': el.style.setProperty('overflow', 'visible', 'important'); break; case 'classList': if (handle.remove) { handle.remove.split(' ').forEach(cls => el.classList.remove(cls)); } break; case 'click': if (el && el.getAttribute('data-unfolded') !== '1') { el.click(); el.setAttribute('data-unfolded', '1'); } break; } }); } } } // 使用 requestAnimationFrame 轮询展开(优化性能) function scheduleUnfold() { if (unfoldAttempts > MAX_ATTEMPTS) return; performUnfold(); unfoldAttempts++; unfoldTimer = requestAnimationFrame(scheduleUnfold); } // 启动展开 function startUnfold() { scheduleUnfold(); // 页面完全加载后额外执行几次确保动态内容 window.addEventListener('load', () => { performUnfold(); setTimeout(performUnfold, 300); setTimeout(performUnfold, 800); }); } // 监听 SPA 页面 URL 变化重新展开 let lastUrl = location.href; const urlObserver = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; unfoldAttempts = 0; // 重置计数 performUnfold(); } }); urlObserver.observe(document, { subtree: true, childList: true }); // 启动 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startUnfold); } else { startUnfold(); } })(); // ==================== 功能2:记录页面滚动位置 ==================== (function() { const STORAGE_KEY = 'lemonScrollBox_v3'; let currentBoxObj = {}; // 获取元素的 XPath(简化版) function getXPath(element) { if (!element || element.nodeType !== 1) return ''; if (element.id) return `//*[@id="${element.id}"]`; let paths = []; for (; element && element.nodeType === 1; element = element.parentNode) { let index = 0; for (let sibling = element.previousSibling; sibling; sibling = sibling.previousSibling) { if (sibling.nodeType === 1 && sibling.nodeName === element.nodeName) index++; } const tagName = element.nodeName.toLowerCase(); const pathIndex = index ? `[${index + 1}]` : ''; paths.unshift(`${tagName}${pathIndex}`); } return '/' + paths.join('/'); } function getScrollContainer() { return document.scrollingElement || document.documentElement; } function saveScrollPosition() { try { const container = getScrollContainer(); if (!container) return; const url = location.href; const xpath = getXPath(container); currentBoxObj[url] = { xpath: xpath, pos: container.scrollTop, id: container.id || '', className: container.className || '' }; localStorage.setItem(STORAGE_KEY, JSON.stringify(currentBoxObj)); } catch(e) {} } function restoreScrollPosition() { try { const saved = localStorage.getItem(STORAGE_KEY); if (!saved) return; currentBoxObj = JSON.parse(saved); const url = location.href; const data = currentBoxObj[url]; if (!data) return; runNeed( () => { let container = getScrollContainer(); if (data.xpath && data.xpath !== '/html') { try { const result = document.evaluate(data.xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); if (result.singleNodeValue) container = result.singleNodeValue; } catch(e) {} } return container && container.scrollHeight > window.innerHeight; }, () => { let container = getScrollContainer(); try { const result = document.evaluate(data.xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); if (result.singleNodeValue) container = result.singleNodeValue; } catch(e) {} if (container) { setTimeout(() => { container.scrollTop = data.pos; }, 100); } }, { count: 10, delay: 300 } ); } catch(e) {} } function initScrollMemory() { if (!localStorage.getItem(STORAGE_KEY)) { localStorage.setItem(STORAGE_KEY, '{}'); currentBoxObj = {}; } else { try { currentBoxObj = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {}; } catch(e) { currentBoxObj = {}; } } const throttledSave = throttle(saveScrollPosition, 500); document.addEventListener('scroll', throttledSave, { passive: true }); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', restoreScrollPosition); } else { restoreScrollPosition(); } // 监听 URL 变化(SPA) let lastUrl = location.href; const observer = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; restoreScrollPosition(); } }); observer.observe(document, { subtree: true, childList: true }); window.addEventListener('popstate', restoreScrollPosition); } initScrollMemory(); })(); // ==================== 功能3:浮动按钮(回顶/刷新/DeepSeek) ==================== (function() { const TOP_BTN_ID = 'merged-back-top-btn'; const REFRESH_BTN_ID = 'merged-refresh-btn'; const DEEPSEEK_BTN_ID = 'merged-deepseek-btn'; // 注入样式(按钮尺寸33px,右距6px,垂直间距22px) const buttonStyles = ` #${TOP_BTN_ID}, #${REFRESH_BTN_ID}, #${DEEPSEEK_BTN_ID} { position: fixed; right: 6px; width: 33px; height: 33px; border-radius: 50%; background-color: rgba(60, 60, 60, 0.5); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); box-shadow: 0 2px 8px rgba(0,0,0,0.15); display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 9999; opacity: 0; transform: scale(0.8); transition: opacity 0.25s ease, transform 0.2s ease; pointer-events: none; border: 1px solid rgba(255,255,255,0.2); } #${TOP_BTN_ID} { top: 66.6vh; } #${REFRESH_BTN_ID} { top: calc(66.6vh + 33px + 22px); } #${DEEPSEEK_BTN_ID} { top: calc(66.6vh + 33px + 22px + 33px + 22px); } #${TOP_BTN_ID}.visible, #${REFRESH_BTN_ID}.visible, #${DEEPSEEK_BTN_ID}.visible { opacity: 1; transform: scale(1); pointer-events: auto; } /* 回顶箭头 */ #${TOP_BTN_ID}::before { content: ''; width: 10px; height: 10px; border-left: 2.2px solid white; border-bottom: 2.2px solid white; transform: rotate(135deg); margin-top: 4px; } /* 刷新图标(旋转环+箭头) */ #${REFRESH_BTN_ID}::before { content: ''; width: 14px; height: 14px; border: 2.2px solid white; border-right-color: transparent; border-radius: 50%; transform: rotate(-45deg); margin-top: 2px; } #${REFRESH_BTN_ID}::after { content: ''; position: absolute; width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-bottom: 6px solid white; transform: rotate(135deg) translate(8px, -2px); opacity: 0.9; } /* DeepSeek 按钮(DS 字母) */ #${DEEPSEEK_BTN_ID} { font-weight: 600; font-size: 16px; color: white; font-family: Arial, sans-serif; } #${DEEPSEEK_BTN_ID}::before { content: 'DS'; line-height: 1; text-shadow: 0 1px 2px rgba(0,0,0,0.2); } #${TOP_BTN_ID}:active, #${REFRESH_BTN_ID}:active, #${DEEPSEEK_BTN_ID}:active { background-color: rgba(80, 80, 80, 0.6); transform: scale(0.9); } @media (prefers-color-scheme: dark) { #${TOP_BTN_ID}, #${REFRESH_BTN_ID}, #${DEEPSEEK_BTN_ID} { background-color: rgba(220, 220, 220, 0.2); border-color: rgba(255,255,255,0.1); } #${TOP_BTN_ID}:active, #${REFRESH_BTN_ID}:active, #${DEEPSEEK_BTN_ID}:active { background-color: rgba(255, 255, 255, 0.25); } } `; addStyle_Buttons(buttonStyles); // 创建按钮 const topBtn = document.createElement('div'); topBtn.id = TOP_BTN_ID; topBtn.setAttribute('aria-label', '回到顶部'); document.body.appendChild(topBtn); const refreshBtn = document.createElement('div'); refreshBtn.id = REFRESH_BTN_ID; refreshBtn.setAttribute('aria-label', '刷新页面'); document.body.appendChild(refreshBtn); let deepseekBtn = null; if (CONFIG.ENABLE_DEEPSEEK_BTN) { deepseekBtn = document.createElement('div'); deepseekBtn.id = DEEPSEEK_BTN_ID; deepseekBtn.setAttribute('aria-label', '跳转到 DeepSeek'); document.body.appendChild(deepseekBtn); } // 显示/隐藏控制(requestAnimationFrame 防抖) let ticking = false; function updateButtonsVisibility() { const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; const shouldShow = scrollTop > CONFIG.SCROLL_THRESHOLD; const action = shouldShow ? 'add' : 'remove'; topBtn.classList[action]('visible'); refreshBtn.classList[action]('visible'); if (deepseekBtn) deepseekBtn.classList[action]('visible'); ticking = false; } function onScroll() { if (!ticking) { requestAnimationFrame(updateButtonsVisibility); ticking = true; } } window.addEventListener('scroll', onScroll, { passive: true }); // 绑定事件 topBtn.addEventListener('click', (e) => { e.stopPropagation(); window.scrollTo({ top: 0, behavior: 'smooth' }); }); refreshBtn.addEventListener('click', (e) => { e.stopPropagation(); location.reload(); }); if (deepseekBtn) { deepseekBtn.addEventListener('click', (e) => { e.stopPropagation(); window.location.href = CONFIG.DEEPSEEK_URL; }); } // 初始化检查 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', updateButtonsVisibility); } else { updateButtonsVisibility(); } // SPA 路由变化时重新检查 let lastUrl = location.href; const observer = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(updateButtonsVisibility, 100); } }); observer.observe(document, { subtree: true, childList: true }); })(); })();