GitHub发布平台筛选器

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

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

  1. // ==UserScript==
  2. // @name GitHub发布平台筛选器
  3. // @name:en GitHub Release Platform Filter
  4. // @namespace http://tampermonkey.net/
  5. // @version 0.3
  6. // @description 筛选GitHub发布资源的平台,优化发布说明显示
  7. // @description:en Filter GitHub release assets by platform and optimize release notes display
  8. // @author EEP
  9. // @match https://github.com/*
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. // 添加调试信息
  19. const debug = {
  20. log: function (message) {
  21. console.log('[GitHub Release Filter]', message);
  22. },
  23. };
  24.  
  25. debug.log('脚本开始加载');
  26.  
  27. // 样式定义
  28. const styles = `
  29. .markdown-body.my-3 {
  30. position: relative;
  31. max-height: 300px;
  32. overflow: hidden;
  33. transition: max-height 0.3s ease-in-out;
  34. padding-bottom: 40px;
  35. }
  36. .markdown-body.my-3.expanded {
  37. max-height: none;
  38. padding-bottom: 40px;
  39. }
  40. .toggle-button {
  41. position: absolute;
  42. bottom: 0;
  43. left: 0;
  44. right: 0;
  45. text-align: center;
  46. padding: 10px;
  47. cursor: pointer;
  48. color: #0366d6;
  49. font-weight: 600;
  50. width: 100%;
  51. margin: 0;
  52. height: 40px;
  53. box-sizing: border-box;
  54. background: none;
  55. }
  56. .markdown-body.my-3:not(.expanded) .toggle-button {
  57. background: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
  58. }
  59. .markdown-body.my-3.expanded .toggle-button {
  60. border-top: 1px solid #e1e4e8;
  61. }
  62. .toggle-button:hover {
  63. text-decoration: underline;
  64. }
  65. .platform-filter {
  66. position: fixed;
  67. top: 60px;
  68. right: 16px;
  69. z-index: 1000;
  70. background: white;
  71. border-radius: 6px;
  72. box-shadow: 0 1px 3px rgba(0,0,0,0.12);
  73. }
  74. .platform-filter-button {
  75. padding: 8px 12px;
  76. border: 1px solid #d1d5da;
  77. border-radius: 6px;
  78. background-color: #f6f8fa;
  79. color: #24292e;
  80. cursor: pointer;
  81. display: flex;
  82. align-items: center;
  83. gap: 8px;
  84. font-size: 14px;
  85. font-weight: 500;
  86. white-space: nowrap;
  87. }
  88. .platform-filter-button:hover {
  89. background-color: #f3f4f6;
  90. border-color: #bbb;
  91. }
  92. .platform-filter-button svg {
  93. width: 16px;
  94. height: 16px;
  95. }
  96. .platform-dropdown {
  97. position: absolute;
  98. top: 100%;
  99. right: 0;
  100. margin-top: 4px;
  101. background: white;
  102. border: 1px solid #e1e4e8;
  103. border-radius: 6px;
  104. box-shadow: 0 8px 24px rgba(149,157,165,0.2);
  105. display: none;
  106. min-width: 200px;
  107. }
  108. .platform-dropdown.show {
  109. display: block;
  110. }
  111. .platform-option {
  112. padding: 8px 16px;
  113. cursor: pointer;
  114. display: flex;
  115. align-items: center;
  116. gap: 8px;
  117. }
  118. .platform-option:hover {
  119. background-color: #f6f8fa;
  120. }
  121. .platform-option.selected {
  122. background-color: #f1f8ff;
  123. color: #0366d6;
  124. }
  125. .platform-option svg {
  126. width: 16px;
  127. height: 16px;
  128. }
  129. .Box-row.d-flex.flex-column.flex-md-row.hidden-asset {
  130. display: none !important;
  131. }
  132. `;
  133.  
  134. // 添加SVG图标
  135. const ICONS = {
  136. 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>`,
  137. };
  138.  
  139. // 主要功能实现
  140. function initFeaturesPanels() {
  141. debug.log('开始初始化面板');
  142. const panels = document.querySelectorAll('.markdown-body.my-3');
  143. debug.log(`找到 ${panels.length} 个面板`);
  144.  
  145. panels.forEach((panel) => {
  146. // 检查内容高度是否需要添加展开/收起功能
  147. if (panel.scrollHeight > 300) {
  148. // 检查是否已经存在按钮
  149. let toggleButton = panel.querySelector('.toggle-button');
  150.  
  151. if (!toggleButton) {
  152. // 如果不存在按钮,则创建新按钮
  153. toggleButton = document.createElement('div');
  154. toggleButton.className = 'toggle-button';
  155. panel.appendChild(toggleButton);
  156. }
  157.  
  158. // 根据当前状态设置按钮文本
  159. const isExpanded = panel.classList.contains('expanded');
  160. toggleButton.textContent = isExpanded ? '收起' : '展开更多';
  161.  
  162. // 更新或添加点击事件
  163. toggleButton.onclick = () => {
  164. const isExpanded = panel.classList.contains('expanded');
  165. panel.classList.toggle('expanded');
  166. toggleButton.textContent = !isExpanded ? '收起' : '展开更多';
  167. if (!isExpanded) {
  168. panel.scrollIntoView({ behavior: 'smooth' });
  169. }
  170. };
  171. }
  172. });
  173. }
  174.  
  175. // 监听页面变化,确保在动态加载的内容上也能生效
  176. const observer = new MutationObserver((mutations) => {
  177. for (const mutation of mutations) {
  178. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  179. const hasMarkdownBody = Array.from(mutation.addedNodes).some(
  180. (node) =>
  181. node.nodeType === 1 &&
  182. (node.classList?.contains('markdown-body') ||
  183. node.querySelector?.('.markdown-body'))
  184. );
  185. if (hasMarkdownBody) {
  186. debug.log('检测到新的markdown内容,重新初始化面板');
  187. initFeaturesPanels();
  188. break;
  189. }
  190. }
  191. }
  192. });
  193.  
  194. // 获取当前系统平台
  195. function getCurrentPlatform() {
  196. const platform = navigator.platform.toLowerCase();
  197. const userAgent = navigator.userAgent.toLowerCase();
  198. if (platform.includes('win')) return 'windows';
  199. if (platform.includes('mac')) return 'macos';
  200. if (platform.includes('linux')) return 'linux';
  201. if (userAgent.includes('android')) return 'android';
  202. return 'unknown';
  203. }
  204.  
  205. // 判断资源是否属于指定平台
  206. function isAssetForPlatform(assetText, platform) {
  207. const text = assetText.toLowerCase();
  208. // 检查是否是md5文件或源代码文件
  209. const isMd5File = text.includes('md5') || text.endsWith('.md5');
  210. const isSourceCode =
  211. text.includes('source') ||
  212. text.includes('src') ||
  213. text.endsWith('.zip') ||
  214. text.endsWith('.tar.gz');
  215.  
  216. // 如果是md5文件,根据文件名中的平台信息判断
  217. if (isMd5File) {
  218. switch (platform) {
  219. case 'windows':
  220. return text.includes('win') || text.includes('windows');
  221. case 'macos':
  222. return (
  223. text.includes('mac') ||
  224. text.includes('darwin') ||
  225. text.includes('osx')
  226. );
  227. case 'linux':
  228. return text.includes('linux') || text.includes('musl');
  229. case 'android':
  230. return text.includes('android');
  231. default:
  232. return false;
  233. }
  234. }
  235.  
  236. // 如果是源代码文件,对所有平台都显示
  237. if (isSourceCode) {
  238. return true;
  239. }
  240.  
  241. // 其他文件按照原有逻辑判断
  242. switch (platform) {
  243. case 'windows':
  244. return (
  245. text.includes('windows') ||
  246. text.includes('.exe') ||
  247. text.includes('.msi') ||
  248. text.includes('win') ||
  249. text.includes('win32') ||
  250. text.includes('win64')
  251. );
  252. case 'macos':
  253. return (
  254. text.includes('macos') ||
  255. text.includes('darwin') ||
  256. text.includes('.dmg') ||
  257. text.includes('mac') ||
  258. text.includes('osx') ||
  259. text.includes('apple') ||
  260. text.includes('x64.pkg') ||
  261. text.includes('arm64.pkg')
  262. );
  263. case 'linux':
  264. return (
  265. text.includes('linux') ||
  266. text.includes('.deb') ||
  267. text.includes('.rpm') ||
  268. text.includes('.appimage') ||
  269. text.includes('x86_64') ||
  270. text.includes('amd64') ||
  271. text.includes('arm64')
  272. );
  273. case 'android':
  274. return (
  275. text.includes('android') ||
  276. text.includes('.apk') ||
  277. text.includes('.aab') ||
  278. text.includes('arm') ||
  279. text.includes('aarch64')
  280. );
  281. default:
  282. return false;
  283. }
  284. }
  285.  
  286. // 获取当前系统平台
  287. const currentPlatform = getCurrentPlatform();
  288. // 获取保存的平台选择,如果没有保存过,则只选中当前平台
  289. let selectedPlatforms = GM_getValue('selectedPlatforms', null);
  290. if (selectedPlatforms === null) {
  291. selectedPlatforms = [currentPlatform];
  292. GM_setValue('selectedPlatforms', selectedPlatforms);
  293. }
  294.  
  295. // 筛选资源
  296. function filterAssets() {
  297. const assetsContainer = document.querySelector('.Box--condensed');
  298. if (!assetsContainer) return;
  299.  
  300. const assetsList = assetsContainer.querySelectorAll(
  301. '.Box-row.d-flex.flex-column.flex-md-row'
  302. );
  303. if (selectedPlatforms.length === 0) {
  304. // 如果没有选择任何平台,显示所有资源
  305. assetsList.forEach((asset) => {
  306. asset.classList.remove('hidden-asset');
  307. });
  308. return;
  309. }
  310.  
  311. assetsList.forEach((asset) => {
  312. const assetText = asset.textContent;
  313. const shouldShow = selectedPlatforms.some((platform) =>
  314. isAssetForPlatform(assetText, platform)
  315. );
  316. asset.classList.toggle('hidden-asset', !shouldShow);
  317. });
  318.  
  319. debug.log(`筛选完成,当前选中平台: ${selectedPlatforms.join(', ')}`);
  320. }
  321.  
  322. // 初始化资源列表筛选
  323. function initAssetsFilter() {
  324. debug.log('开始初始化资源列表筛选');
  325. const assetsContainer = document.querySelector('.Box--condensed');
  326. if (!assetsContainer) {
  327. debug.log('未找到资源容器,等待100ms后重试');
  328. setTimeout(initAssetsFilter, 100);
  329. return;
  330. }
  331.  
  332. // 检查是否已经存在筛选器
  333. if (document.querySelector('.platform-filter')) {
  334. debug.log('筛选器已存在,重新应用筛选规则');
  335. filterAssets();
  336. return;
  337. }
  338.  
  339. // 检查是否有资源列表
  340. const assetsList = assetsContainer.querySelectorAll(
  341. '.Box-row.d-flex.flex-column.flex-md-row'
  342. );
  343. if (assetsList.length === 0) {
  344. debug.log('未找到资源列表,等待100ms后重试');
  345. setTimeout(initAssetsFilter, 100);
  346. return;
  347. }
  348.  
  349. debug.log(`找到 ${assetsList.length} 个资源项`);
  350.  
  351. // 创建一个 MutationObserver 来监听资源列表的变化
  352. const assetsObserver = new MutationObserver((mutations) => {
  353. for (const mutation of mutations) {
  354. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  355. const hasAssets = Array.from(mutation.addedNodes).some(
  356. (node) => node.nodeType === 1 && node.classList?.contains('Box-row')
  357. );
  358. if (hasAssets) {
  359. debug.log('检测到新的资源列表,重新应用筛选');
  360. filterAssets();
  361. }
  362. }
  363. }
  364. });
  365.  
  366. // 开始观察资源列表容器
  367. assetsObserver.observe(assetsContainer, {
  368. childList: true,
  369. subtree: true,
  370. });
  371.  
  372. // 创建筛选器容器
  373. const filterContainer = document.createElement('div');
  374. filterContainer.className = 'platform-filter';
  375.  
  376. // 创建筛选按钮
  377. const filterButton = document.createElement('button');
  378. filterButton.className = 'platform-filter-button';
  379. filterButton.innerHTML = `${ICONS.settings} <span>平台筛选</span>`;
  380.  
  381. // 创建下拉菜单
  382. const dropdown = document.createElement('div');
  383. dropdown.className = 'platform-dropdown';
  384.  
  385. // 添加平台选项
  386. const platforms = [
  387. { id: 'windows', name: 'Windows' },
  388. { id: 'macos', name: 'macOS' },
  389. { id: 'linux', name: 'Linux' },
  390. { id: 'android', name: 'Android' },
  391. ];
  392.  
  393. platforms.forEach((platform) => {
  394. const option = document.createElement('div');
  395. option.className = `platform-option ${
  396. selectedPlatforms.includes(platform.id) ? 'selected' : ''
  397. }`;
  398. option.textContent = platform.name;
  399. option.onclick = (e) => {
  400. e.stopPropagation();
  401. const index = selectedPlatforms.indexOf(platform.id);
  402. if (index === -1) {
  403. selectedPlatforms.push(platform.id);
  404. } else {
  405. selectedPlatforms.splice(index, 1);
  406. }
  407. option.classList.toggle('selected');
  408. GM_setValue('selectedPlatforms', selectedPlatforms);
  409. filterAssets();
  410. };
  411. dropdown.appendChild(option);
  412. });
  413.  
  414. // 添加按钮点击事件
  415. filterButton.onclick = () => {
  416. dropdown.classList.toggle('show');
  417. };
  418.  
  419. // 点击其他地方关闭下拉菜单
  420. document.addEventListener('click', (e) => {
  421. if (!filterContainer.contains(e.target)) {
  422. dropdown.classList.remove('show');
  423. }
  424. });
  425.  
  426. // 组装并添加到页面
  427. filterContainer.appendChild(filterButton);
  428. filterContainer.appendChild(dropdown);
  429. const mainContent = document.querySelector('main');
  430. const releaseHeader = document.querySelector('.release-header');
  431. if (releaseHeader) {
  432. releaseHeader.style.position = 'relative';
  433. releaseHeader.appendChild(filterContainer);
  434. } else if (mainContent) {
  435. mainContent.appendChild(filterContainer);
  436. } else {
  437. document.body.appendChild(filterContainer);
  438. }
  439.  
  440. // 初始筛选
  441. filterAssets();
  442. }
  443.  
  444. // 添加样式到页面
  445. function addStyles() {
  446. debug.log('添加样式到页面');
  447. if (!document.querySelector('style[data-github-release-filter]')) {
  448. const styleElement = document.createElement('style');
  449. styleElement.setAttribute('data-github-release-filter', 'true');
  450. styleElement.textContent = styles;
  451. document.head.appendChild(styleElement);
  452. }
  453. }
  454.  
  455. // 检查是否在releases页面
  456. function isReleasesPage() {
  457. return window.location.href.toLowerCase().includes('releases');
  458. }
  459.  
  460. // 等待页面加载完成后再初始化
  461. function initializeFeatures() {
  462. debug.log('检查页面类型');
  463. if (!isReleasesPage()) {
  464. debug.log('当前不是releases页面,跳过初始化');
  465. return;
  466. }
  467. debug.log('开始初始化所有功能');
  468. // 确保样式已添加
  469. if (!document.querySelector('style[data-github-release-filter]')) {
  470. addStyles();
  471. }
  472. // 先初始化发布说明功能
  473. initFeaturesPanels();
  474. startObserver();
  475. // 然后初始化资源列表筛选功能
  476. initAssetsFilter();
  477. }
  478.  
  479. // 检查并初始化功能
  480. function checkAndInitialize() {
  481. debug.log('检查页面状态并初始化功能');
  482. if (!isReleasesPage()) {
  483. debug.log('当前不是releases页面,跳过初始化');
  484. return;
  485. }
  486. // 确保样式已添加
  487. addStyles();
  488. // 确保页面已经完全加载
  489. if (
  490. document.readyState === 'complete' ||
  491. document.readyState === 'interactive'
  492. ) {
  493. // 先初始化发布说明功能
  494. const markdownContent = document.querySelector('.markdown-body.my-3');
  495. if (markdownContent) {
  496. debug.log('找到发布说明内容,开始初始化');
  497. addStyles();
  498. initFeaturesPanels();
  499. startObserver();
  500. }
  501.  
  502. // 检查资源列表是否存在
  503. const releaseContent = document.querySelector('.Box--condensed');
  504. if (releaseContent) {
  505. debug.log('找到资源列表,开始初始化');
  506. // 确保所有动态内容都已加载
  507. setTimeout(() => {
  508. initAssetsFilter();
  509. // 持续监听可能的动态内容加载
  510. const contentObserver = new MutationObserver((mutations) => {
  511. for (const mutation of mutations) {
  512. if (
  513. mutation.type === 'childList' &&
  514. mutation.addedNodes.length > 0
  515. ) {
  516. const hasMarkdownBody = Array.from(mutation.addedNodes).some(
  517. (node) =>
  518. node.nodeType === 1 &&
  519. (node.classList?.contains('markdown-body') ||
  520. node.querySelector?.('.markdown-body'))
  521. );
  522. if (hasMarkdownBody) {
  523. initFeaturesPanels();
  524. }
  525. const hasAssets = Array.from(mutation.addedNodes).some(
  526. (node) =>
  527. node.nodeType === 1 && node.classList?.contains('Box-row')
  528. );
  529. if (hasAssets) {
  530. initAssetsFilter();
  531. }
  532. }
  533. }
  534. });
  535. contentObserver.observe(releaseContent, {
  536. childList: true,
  537. subtree: true,
  538. });
  539. }, 500);
  540. } else {
  541. debug.log('未找到资源列表,等待100ms后重试');
  542. setTimeout(checkAndInitialize, 100);
  543. }
  544. } else {
  545. debug.log('页面未完全加载,等待加载完成');
  546. window.addEventListener('load', checkAndInitialize);
  547. }
  548. }
  549.  
  550. // 初始检查
  551. checkAndInitialize();
  552.  
  553. // 监听Turbo Drive页面切换事件
  554. document.addEventListener('turbo:load', () => {
  555. debug.log('检测到页面切换,重新检查并初始化功能');
  556. checkAndInitialize();
  557. });
  558.  
  559. // 监听导航事件
  560. window.addEventListener('popstate', () => {
  561. debug.log('检测到导航事件,重新检查并初始化功能');
  562. checkAndInitialize();
  563. });
  564.  
  565. function startObserver() {
  566. debug.log('开始观察页面变化');
  567. observer.observe(document.body, {
  568. childList: true,
  569. subtree: true,
  570. });
  571. }
  572. })();

QingJ © 2025

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