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