Greasy Fork

Greasy Fork is available in English.

网页自动展开(全站适用·防误跳转)

自动展开任意网站的折叠内容,智能识别展开按钮,避免误触跳转链接。

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

// ==UserScript==
// @name         网页自动展开(全站适用·防误跳转)
// @version      1.0.3
// @description  自动展开任意网站的折叠内容,智能识别展开按钮,避免误触跳转链接。
// @namespace    Kiwifruit13
// @match        *://*/*
// @run-at       document-idle
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  // ============= 排除名单(在此添加已知有问题的网站) =============
  const EXCLUDE_HOSTS = [
    'www.toutiao.com',
    'm.toutiao.com',
    'zh.moegirl.org.cn',
    'moegirl.org.cn',
    'www.zhihu.com', // 可选:若知乎展开异常也可排除
    'v.douyin.com', // 抖音网页版可能受影响
    // 可继续添加其他问题站点
  ];

  // 检查当前页面是否在排除名单中
  const currentHost = location.hostname;
  if (EXCLUDE_HOSTS.some(host => currentHost === host || currentHost.endsWith('.' + host))) {
    return; // 直接退出,不执行任何逻辑
  }

  // ============= 以下为原有自动展开逻辑 =============
  const EXPANDED_ATTR = 'data-auto-expanded';
  const CLICKED_ATTR = 'data-auto-clicked';
  const EXPAND_KEYWORDS = [
    '展开', '查看', '阅读', '显示', '更多', '全文', '全部', '继续',
    'Read', 'More', 'Full', 'Expand', 'Continue', 'Show', 'View'
  ];

  function isSafeExpandButton(el) {
    if (!el || el.nodeType !== 1) return false;
    if (el.hasAttribute(CLICKED_ATTR)) return false;
    const rect = el.getBoundingClientRect();
    if (rect.height > 60 || rect.width > 300) return false;
    const text = (el.innerText || el.textContent || '').trim();
    if (text.length === 0 || text.length > 40) return false;
    if (/登录|关注|订阅|点赞|收藏|分享|评论|回复|举报|广告|下载|安装|打开APP|跳转|前往/i.test(text)) {
      return false;
    }
    const hasKeyword = EXPAND_KEYWORDS.some(kw => text.includes(kw));
    if (!hasKeyword) return false;
    if (el.tagName === 'A') {
      const href = el.getAttribute('href') || '';
      if (href && !/^(\s*#|javascript:|mailto:|tel:)/i.test(href)) {
        return false;
      }
    }
    return el.tagName === 'BUTTON' ||
           el.tagName === 'A' ||
           el.role === 'button' ||
           el.onclick ||
           el.classList.length > 0;
  }

  function isCollapsedContainer(el) {
    if (el.hasAttribute(EXPANDED_ATTR)) return false;
    if (el.tagName === 'BODY' || el.tagName === 'HTML') return false; // 保护 body/html
    const cs = window.getComputedStyle(el);
    const maxHeight = parseFloat(cs.maxHeight);
    const hasMaxHeight = maxHeight > 0 && maxHeight < 600;
    const hasOverflowHidden = cs.overflow === 'hidden' || cs.overflowY === 'hidden';
    const isTaller = el.scrollHeight > el.clientHeight + 20;

    // 防止误伤滚动容器
    const rect = el.getBoundingClientRect();
    const isFullViewport = rect.height >= window.innerHeight * 0.85;
    const isScrollable = /auto|scroll/.test(cs.overflowY);
    if (isFullViewport && isScrollable) return false;

    return hasMaxHeight && hasOverflowHidden && isTaller;
  }

  function removeMask(el) {
    if (!el.className) return;
    const cls = el.className.replace(/\s+/g, '.');
    const tag = el.tagName.toLowerCase();
    const selector = `${tag}${cls ? '.' + cls : ''}`;
    const style = document.createElement('style');
    style.textContent = `
      ${selector}::before,
      ${selector}::after {
        display: none !important;
        content: "" !important;
        background: transparent !important;
      }
    `;
    document.head.appendChild(style);
  }

  function autoExpand() {
    document.querySelectorAll('button, a, [role="button"], div, span, p').forEach(el => {
      if (isSafeExpandButton(el)) {
        try {
          el.click();
          el.setAttribute(CLICKED_ATTR, 'true');
        } catch (e) { /* ignore */ }
      }
    });

    document.querySelectorAll('div, section, article, p, .content, .text').forEach(el => {
      if (isCollapsedContainer(el)) {
        el.style.maxHeight = 'none';
        el.style.height = 'auto';
        el.style.overflow = 'visible';
        el.setAttribute(EXPANDED_ATTR, 'true');
        removeMask(el);
      }
    });
  }

  autoExpand();

  let observerTimer;
  const observer = new MutationObserver(() => {
    clearTimeout(observerTimer);
    observerTimer = setTimeout(autoExpand, 400);
  });
  observer.observe(document.body, { childList: true, subtree: true });

  let scrollTimer;
  window.addEventListener('scroll', () => {
    clearTimeout(scrollTimer);
    scrollTimer = setTimeout(autoExpand, 400);
  });

  if (document.readyState === 'complete') {
    setTimeout(autoExpand, 1000);
  } else {
    window.addEventListener('load', () => setTimeout(autoExpand, 1000));
  }

})();