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