GitHub发布平台筛选器

筛选GitHub发布资源的平台,优化发布说明显示

目前为 2025-02-15 提交的版本。查看 最新版本

// ==UserScript==
// @name         GitHub发布平台筛选器
// @name:en      GitHub Release Platform Filter
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  筛选GitHub发布资源的平台,优化发布说明显示
// @description:en  Filter GitHub release assets by platform and optimize release notes display
// @author       EEP
// @match        https://github.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  // 添加调试信息
  const debug = {
    log: function (message) {
      console.log('[GitHub Release Filter]', message);
    },
  };

  debug.log('脚本开始加载');

  // 样式定义
  const styles = `
        .markdown-body.my-3 {
            position: relative;
            max-height: 300px;
            overflow: hidden;
            transition: max-height 0.3s ease-in-out;
            padding-bottom: 40px;
        }
        .markdown-body.my-3.expanded {
            max-height: none;
            padding-bottom: 40px;
        }
        .toggle-button {
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            text-align: center;
            padding: 10px;
            cursor: pointer;
            color: #0366d6;
            font-weight: 600;
            width: 100%;
            margin: 0;
            height: 40px;
            box-sizing: border-box;
            background: none;
        }
        .markdown-body.my-3:not(.expanded) .toggle-button {
            background: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
        }
        .markdown-body.my-3.expanded .toggle-button {
            border-top: 1px solid #e1e4e8;
        }
        .toggle-button:hover {
            text-decoration: underline;
        }
        .platform-filter {
            position: fixed;
            top: 60px;
            right: 16px;
            z-index: 1000;
            background: white;
            border-radius: 6px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.12);
        }
        .platform-filter-button {
            padding: 8px 12px;
            border: 1px solid #d1d5da;
            border-radius: 6px;
            background-color: #f6f8fa;
            color: #24292e;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 14px;
            font-weight: 500;
            white-space: nowrap;
        }
        .platform-filter-button:hover {
            background-color: #f3f4f6;
            border-color: #bbb;
        }
        .platform-filter-button svg {
            width: 16px;
            height: 16px;
        }
        .platform-dropdown {
            position: absolute;
            top: 100%;
            right: 0;
            margin-top: 4px;
            background: white;
            border: 1px solid #e1e4e8;
            border-radius: 6px;
            box-shadow: 0 8px 24px rgba(149,157,165,0.2);
            display: none;
            min-width: 200px;
        }
        .platform-dropdown.show {
            display: block;
        }
        .platform-option {
            padding: 8px 16px;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .platform-option:hover {
            background-color: #f6f8fa;
        }
        .platform-option.selected {
            background-color: #f1f8ff;
            color: #0366d6;
        }
        .platform-option svg {
            width: 16px;
            height: 16px;
        }
        .Box-row.d-flex.flex-column.flex-md-row.hidden-asset {
            display: none !important;
        }
    `;

  // 添加SVG图标
  const ICONS = {
    settings: `<svg viewBox="0 0 16 16" fill="currentColor"><path fill-rule="evenodd" d="M7.429 1.525a6.593 6.593 0 011.142 0c.036.003.108.036.137.146l.289 1.105c.147.56.55.967.997 1.189.174.086.341.183.501.29.417.278.97.423 1.53.27l1.102-.303c.11-.03.175.016.195.046.219.31.41.641.573.989.014.031.022.11-.059.19l-.815.806c-.411.406-.562.957-.53 1.456a4.588 4.588 0 010 .582c-.032.499.119 1.05.53 1.456l.815.806c.08.08.073.159.059.19a6.494 6.494 0 01-.573.99c-.02.029-.086.074-.195.045l-1.103-.303c-.559-.153-1.112-.008-1.529.27-.16.107-.327.204-.5.29-.449.222-.851.628-.998 1.189l-.289 1.105c-.029.11-.101.143-.137.146a6.613 6.613 0 01-1.142 0c-.036-.003-.108-.037-.137-.146l-.289-1.105c-.147-.56-.55-.967-.997-1.189a4.502 4.502 0 01-.501-.29c-.417-.278-.97-.423-1.53-.27l-1.102.303c-.11.03-.175-.016-.195-.046a6.492 6.492 0 01-.573-.989c-.014-.031-.022-.11.059-.19l.815-.806c.411-.406.562-.957.53-1.456a4.587 4.587 0 010-.582c.032-.499-.119-1.05-.53-1.456l-.815.806c-.08-.08-.073-.159-.059-.19a6.44 6.44 0 01.573-.99c.02-.029.086-.075.195-.045l1.103.303c.559.153 1.112.008 1.529-.27.16-.107.327-.204.5-.29.449-.222.851-.628.998-1.189l.289-1.105c.029-.11.101-.143.137-.146zM8 0c-.236 0-.47.01-.701.03-.743.065-1.29.615-1.458 1.261l-.29 1.106c-.017.066-.078.158-.211.224a5.994 5.994 0 00-.668.386c-.123.082-.233.09-.3.071L3.27 2.776c-.644-.177-1.392.02-1.82.63a7.977 7.977 0 00-.704 1.217c-.315.675-.111 1.422.363 1.891l.815.806c.05.048.098.147.088.294a6.084 6.084 0 000 .772c.01.147-.038.246-.088.294l-.815.806c-.474.469-.678 1.216-.363 1.891.2.428.436.835.704 1.218.428.609 1.176.806 1.82.63l1.103-.303c.066-.019.176-.011.299.071.213.143.436.272.668.386.133.066.194.158.212.224l.289 1.106c.169.646.715 1.196 1.458 1.26a8.094 8.094 0 001.402 0c.743-.064 1.29-.614 1.458-1.26l.29-1.106c.017-.066.078-.158.211-.224a5.98 5.98 0 00.668-.386c.123-.082.233-.09.3-.071l1.102.302c.644.177 1.392-.02 1.82-.63.268-.382.505-.789.704-1.217.315-.675.111-1.422-.363-1.891l-.815-.806c-.05-.048-.098-.147-.088-.294a6.1 6.1 0 000-.772c-.01-.147.038-.246.088-.294l.815-.806c.474-.469.678-1.216.363-1.891a7.992 7.992 0 00-.704-1.218c-.428-.609-1.176-.806-1.82-.63l-1.103.303c-.066.019-.176.011-.299-.071a5.991 5.991 0 00-.668-.386c-.133-.066-.194-.158-.212-.224L10.16 1.29C9.99.645 9.444.095 8.701.031A8.094 8.094 0 008 0zm1.5 8a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zM11 8a3 3 0 11-6 0 3 3 0 016 0z"/></svg>`,
  };

  // 主要功能实现
  function initFeaturesPanels() {
    debug.log('开始初始化面板');
    const panels = document.querySelectorAll('.markdown-body.my-3');
    debug.log(`找到 ${panels.length} 个面板`);

    panels.forEach((panel) => {
      // 检查内容高度是否需要添加展开/收起功能
      if (panel.scrollHeight > 300) {
        // 检查是否已经存在按钮
        let toggleButton = panel.querySelector('.toggle-button');

        if (!toggleButton) {
          // 如果不存在按钮,则创建新按钮
          toggleButton = document.createElement('div');
          toggleButton.className = 'toggle-button';
          panel.appendChild(toggleButton);
        }

        // 根据当前状态设置按钮文本
        const isExpanded = panel.classList.contains('expanded');
        toggleButton.textContent = isExpanded ? '收起' : '展开更多';

        // 更新或添加点击事件
        toggleButton.onclick = () => {
          const isExpanded = panel.classList.contains('expanded');
          panel.classList.toggle('expanded');
          toggleButton.textContent = !isExpanded ? '收起' : '展开更多';
          if (!isExpanded) {
            panel.scrollIntoView({ behavior: 'smooth' });
          }
        };
      }
    });
  }

  // 监听页面变化,确保在动态加载的内容上也能生效
  const observer = new MutationObserver((mutations) => {
    for (const mutation of mutations) {
      if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
        const hasMarkdownBody = Array.from(mutation.addedNodes).some(
          (node) =>
            node.nodeType === 1 &&
            (node.classList?.contains('markdown-body') ||
              node.querySelector?.('.markdown-body'))
        );
        if (hasMarkdownBody) {
          debug.log('检测到新的markdown内容,重新初始化面板');
          initFeaturesPanels();
          break;
        }
      }
    }
  });

  // 获取当前系统平台
  function getCurrentPlatform() {
    const platform = navigator.platform.toLowerCase();
    const userAgent = navigator.userAgent.toLowerCase();
    if (platform.includes('win')) return 'windows';
    if (platform.includes('mac')) return 'macos';
    if (platform.includes('linux')) return 'linux';
    if (userAgent.includes('android')) return 'android';
    return 'unknown';
  }

  // 判断资源是否属于指定平台
  function isAssetForPlatform(assetText, platform) {
    const text = assetText.toLowerCase();
    // 检查是否是md5文件或源代码文件
    const isMd5File = text.includes('md5') || text.endsWith('.md5');
    const isSourceCode =
      text.includes('source') ||
      text.includes('src') ||
      text.endsWith('.zip') ||
      text.endsWith('.tar.gz');

    // 如果是md5文件,根据文件名中的平台信息判断
    if (isMd5File) {
      switch (platform) {
        case 'windows':
          return text.includes('win') || text.includes('windows');
        case 'macos':
          return (
            text.includes('mac') ||
            text.includes('darwin') ||
            text.includes('osx')
          );
        case 'linux':
          return text.includes('linux') || text.includes('musl');
        case 'android':
          return text.includes('android');
        default:
          return false;
      }
    }

    // 如果是源代码文件,对所有平台都显示
    if (isSourceCode) {
      return true;
    }

    // 其他文件按照原有逻辑判断
    switch (platform) {
      case 'windows':
        return (
          text.includes('windows') ||
          text.includes('.exe') ||
          text.includes('.msi') ||
          text.includes('win') ||
          text.includes('win32') ||
          text.includes('win64')
        );
      case 'macos':
        return (
          text.includes('macos') ||
          text.includes('darwin') ||
          text.includes('.dmg') ||
          text.includes('mac') ||
          text.includes('osx') ||
          text.includes('apple') ||
          text.includes('x64.pkg') ||
          text.includes('arm64.pkg')
        );
      case 'linux':
        return (
          text.includes('linux') ||
          text.includes('.deb') ||
          text.includes('.rpm') ||
          text.includes('.appimage') ||
          text.includes('x86_64') ||
          text.includes('amd64') ||
          text.includes('arm64')
        );
      case 'android':
        return (
          text.includes('android') ||
          text.includes('.apk') ||
          text.includes('.aab') ||
          text.includes('arm') ||
          text.includes('aarch64')
        );
      default:
        return false;
    }
  }

  // 获取当前系统平台
  const currentPlatform = getCurrentPlatform();
  // 获取保存的平台选择,如果没有保存过,则只选中当前平台
  let selectedPlatforms = GM_getValue('selectedPlatforms', null);
  if (selectedPlatforms === null) {
    selectedPlatforms = [currentPlatform];
    GM_setValue('selectedPlatforms', selectedPlatforms);
  }

  // 筛选资源
  function filterAssets() {
    const assetsContainer = document.querySelector('.Box--condensed');
    if (!assetsContainer) return;

    const assetsList = assetsContainer.querySelectorAll(
      '.Box-row.d-flex.flex-column.flex-md-row'
    );
    if (selectedPlatforms.length === 0) {
      // 如果没有选择任何平台,显示所有资源
      assetsList.forEach((asset) => {
        asset.classList.remove('hidden-asset');
      });
      return;
    }

    assetsList.forEach((asset) => {
      const assetText = asset.textContent;
      const shouldShow = selectedPlatforms.some((platform) =>
        isAssetForPlatform(assetText, platform)
      );
      asset.classList.toggle('hidden-asset', !shouldShow);
    });

    debug.log(`筛选完成,当前选中平台: ${selectedPlatforms.join(', ')}`);
  }

  // 初始化资源列表筛选
  function initAssetsFilter() {
    debug.log('开始初始化资源列表筛选');
    const assetsContainer = document.querySelector('.Box--condensed');
    if (!assetsContainer) {
      debug.log('未找到资源容器,等待100ms后重试');
      setTimeout(initAssetsFilter, 100);
      return;
    }

    // 检查是否已经存在筛选器
    if (document.querySelector('.platform-filter')) {
      debug.log('筛选器已存在,重新应用筛选规则');
      filterAssets();
      return;
    }

    // 检查是否有资源列表
    const assetsList = assetsContainer.querySelectorAll(
      '.Box-row.d-flex.flex-column.flex-md-row'
    );
    if (assetsList.length === 0) {
      debug.log('未找到资源列表,等待100ms后重试');
      setTimeout(initAssetsFilter, 100);
      return;
    }

    debug.log(`找到 ${assetsList.length} 个资源项`);

    // 创建一个 MutationObserver 来监听资源列表的变化
    const assetsObserver = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
          const hasAssets = Array.from(mutation.addedNodes).some(
            (node) => node.nodeType === 1 && node.classList?.contains('Box-row')
          );
          if (hasAssets) {
            debug.log('检测到新的资源列表,重新应用筛选');
            filterAssets();
          }
        }
      }
    });

    // 开始观察资源列表容器
    assetsObserver.observe(assetsContainer, {
      childList: true,
      subtree: true,
    });

    // 创建筛选器容器
    const filterContainer = document.createElement('div');
    filterContainer.className = 'platform-filter';

    // 创建筛选按钮
    const filterButton = document.createElement('button');
    filterButton.className = 'platform-filter-button';
    filterButton.innerHTML = `${ICONS.settings} <span>平台筛选</span>`;

    // 创建下拉菜单
    const dropdown = document.createElement('div');
    dropdown.className = 'platform-dropdown';

    // 添加平台选项
    const platforms = [
      { id: 'windows', name: 'Windows' },
      { id: 'macos', name: 'macOS' },
      { id: 'linux', name: 'Linux' },
      { id: 'android', name: 'Android' },
    ];

    platforms.forEach((platform) => {
      const option = document.createElement('div');
      option.className = `platform-option ${
        selectedPlatforms.includes(platform.id) ? 'selected' : ''
      }`;
      option.textContent = platform.name;
      option.onclick = (e) => {
        e.stopPropagation();
        const index = selectedPlatforms.indexOf(platform.id);
        if (index === -1) {
          selectedPlatforms.push(platform.id);
        } else {
          selectedPlatforms.splice(index, 1);
        }
        option.classList.toggle('selected');
        GM_setValue('selectedPlatforms', selectedPlatforms);
        filterAssets();
      };
      dropdown.appendChild(option);
    });

    // 添加按钮点击事件
    filterButton.onclick = () => {
      dropdown.classList.toggle('show');
    };

    // 点击其他地方关闭下拉菜单
    document.addEventListener('click', (e) => {
      if (!filterContainer.contains(e.target)) {
        dropdown.classList.remove('show');
      }
    });

    // 组装并添加到页面
    filterContainer.appendChild(filterButton);
    filterContainer.appendChild(dropdown);
    const mainContent = document.querySelector('main');
    const releaseHeader = document.querySelector('.release-header');
    if (releaseHeader) {
      releaseHeader.style.position = 'relative';
      releaseHeader.appendChild(filterContainer);
    } else if (mainContent) {
      mainContent.appendChild(filterContainer);
    } else {
      document.body.appendChild(filterContainer);
    }

    // 初始筛选
    filterAssets();
  }

  // 添加样式到页面
  function addStyles() {
    debug.log('添加样式到页面');
    if (!document.querySelector('style[data-github-release-filter]')) {
      const styleElement = document.createElement('style');
      styleElement.setAttribute('data-github-release-filter', 'true');
      styleElement.textContent = styles;
      document.head.appendChild(styleElement);
    }
  }

  // 检查是否在releases页面
  function isReleasesPage() {
    return window.location.href.toLowerCase().includes('releases');
  }

  // 等待页面加载完成后再初始化
  function initializeFeatures() {
    debug.log('检查页面类型');
    if (!isReleasesPage()) {
      debug.log('当前不是releases页面,跳过初始化');
      return;
    }
    debug.log('开始初始化所有功能');
    // 确保样式已添加
    if (!document.querySelector('style[data-github-release-filter]')) {
      addStyles();
    }
    // 先初始化发布说明功能
    initFeaturesPanels();
    startObserver();
    // 然后初始化资源列表筛选功能
    initAssetsFilter();
  }

  // 检查并初始化功能
  function checkAndInitialize() {
    debug.log('检查页面状态并初始化功能');
    if (!isReleasesPage()) {
      debug.log('当前不是releases页面,跳过初始化');
      return;
    }
    // 确保样式已添加
    addStyles();
    // 确保页面已经完全加载
    if (
      document.readyState === 'complete' ||
      document.readyState === 'interactive'
    ) {
      // 先初始化发布说明功能
      const markdownContent = document.querySelector('.markdown-body.my-3');
      if (markdownContent) {
        debug.log('找到发布说明内容,开始初始化');
        addStyles();
        initFeaturesPanels();
        startObserver();
      }

      // 检查资源列表是否存在
      const releaseContent = document.querySelector('.Box--condensed');
      if (releaseContent) {
        debug.log('找到资源列表,开始初始化');
        // 确保所有动态内容都已加载
        setTimeout(() => {
          initAssetsFilter();
          // 持续监听可能的动态内容加载
          const contentObserver = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
              if (
                mutation.type === 'childList' &&
                mutation.addedNodes.length > 0
              ) {
                const hasMarkdownBody = Array.from(mutation.addedNodes).some(
                  (node) =>
                    node.nodeType === 1 &&
                    (node.classList?.contains('markdown-body') ||
                      node.querySelector?.('.markdown-body'))
                );
                if (hasMarkdownBody) {
                  initFeaturesPanels();
                }
                const hasAssets = Array.from(mutation.addedNodes).some(
                  (node) =>
                    node.nodeType === 1 && node.classList?.contains('Box-row')
                );
                if (hasAssets) {
                  initAssetsFilter();
                }
              }
            }
          });
          contentObserver.observe(releaseContent, {
            childList: true,
            subtree: true,
          });
        }, 500);
      } else {
        debug.log('未找到资源列表,等待100ms后重试');
        setTimeout(checkAndInitialize, 100);
      }
    } else {
      debug.log('页面未完全加载,等待加载完成');
      window.addEventListener('load', checkAndInitialize);
    }
  }

  // 初始检查
  checkAndInitialize();

  // 监听Turbo Drive页面切换事件
  document.addEventListener('turbo:load', () => {
    debug.log('检测到页面切换,重新检查并初始化功能');
    checkAndInitialize();
  });

  // 监听导航事件
  window.addEventListener('popstate', () => {
    debug.log('检测到导航事件,重新检查并初始化功能');
    checkAndInitialize();
  });

  function startObserver() {
    debug.log('开始观察页面变化');
    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });
  }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址