GitHub Release Downloads

Show download count for releases on Github

目前為 2025-03-15 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name GitHub Release Downloads
  3. // @namespace https://github.com/Hibi-10000/GitHubReleaseDownloads
  4. // @version 1.0.1
  5. // @author Hibi_10000
  6. // @license MIT
  7. // @description Show download count for releases on Github
  8. // @source https://github.com/Hibi-10000/GitHubReleaseDownloads
  9. // @icon https://github.githubassets.com/favicons/favicon-dark.png
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @match https://github.com/*
  13. // ==/UserScript==
  14.  
  15. 'use strict';
  16.  
  17. const observer = new MutationObserver(observerFunc)
  18. const initObserver = () => {
  19. const body = document.querySelector("body");
  20. if (body != null) {
  21. observer.observe(body, {childList: true, subtree: true});
  22. } else {
  23. window.setTimeout(initObserver, 1000);
  24. }
  25. }
  26.  
  27. function observerFunc() {
  28. if (/https?:\/\/github.com\/.+?\/.+?\/releases.*/.test(document.URL)) {
  29. run();
  30. }
  31. }
  32.  
  33. const getRepo = () => {
  34. //return document.querySelector('meta[name="octolytics-dimension-repository_nwo"]').content;
  35. return document.URL.match(/(?<=^https?:\/\/github.com\/).+?\/.+?(?=\/releases)/)[0];
  36. }
  37.  
  38. const getReleaseTag = () => {
  39. const ifEmpty = document.URL.replace(/^https?:\/\/github.com\/.+?\/.+?\/releases/, "");
  40. return ifEmpty === "" || ifEmpty === "/" || ifEmpty.startsWith("?") ? null : ifEmpty.match(/(?<=^\/tag\/)[^/?#]+/)[0];
  41. }
  42.  
  43. const getHeader = () => {
  44. const PAT = GM_getValue("GITHUB_PAT");
  45. if (PAT == undefined || PAT == "") {
  46. return null;
  47. } else {
  48. const userAgent = navigator.userAgent;
  49. return {
  50. headers: {
  51. 'User-Agent': userAgent,
  52. 'Authorization': `Bearer ${PAT}`,
  53. }
  54. };
  55. }
  56. }
  57.  
  58. const getLinks = () => {
  59. /** @type {NodeListOf<HTMLAnchorElement>} */
  60. const links = document.querySelectorAll(`a[href^="/${getRepo()}/releases/download/"]`);
  61. return links;
  62. }
  63.  
  64. let isRunning = false;
  65.  
  66. function run() {
  67. for (const link of getLinks()) {
  68. const assetDataElem = link.parentNode.parentNode.children[1];
  69. if (assetDataElem == null) continue;
  70. //grdcounterがない場所が存在するか確認する
  71. if (assetDataElem.querySelector('#grdcounter') == null && !isRunning) {
  72. isRunning = true;
  73. const tag = getReleaseTag();
  74. const response = fetch(`https://api.github.com/repos/${getRepo()}/releases${tag !== null ? `/tags/${tag}` : ""}`, getHeader());
  75. response.then(res => {
  76. if (res.ok) {
  77. res.json().then(json => {
  78. isRunning = false;
  79. setDLCount(json, tag !== null);
  80. });
  81. }
  82. }).catch(error => {
  83. console.error('Error:', error);
  84. });;
  85. }
  86. }
  87. }
  88.  
  89. function setDLCount(json, /** @type {boolean} */ isTag) {
  90. for (const link of getLinks()) {
  91. const name = link.href.match(/(?<=\/)[^/?#]+$/)[0];
  92. const assets = tag => {
  93. return createElement(tag.assets, name, link);
  94. }
  95. if (isTag) {
  96. assets(json);
  97. } else {
  98. const tagName = link.href.match(/(?<=\/download\/)[^/?#]+(?=\/[^/?#]+$)/)[0];
  99. for (const tag of json) {
  100. if (tag.tag_name === tagName && assets(tag)) break;
  101. }
  102. }
  103. }
  104. }
  105.  
  106. function createElement(assets, name, /** @type {Element} */ link) {
  107. for (const asset of assets) {
  108. if (asset.name === name) {
  109. const assetDataElem = link.parentNode.parentNode.children[1];
  110. if (assetDataElem == null) continue;
  111.  
  112. //grdcounterが既に存在するか確認する
  113. if (assetDataElem.querySelector('#grdcounter') != null) continue;
  114.  
  115. const assetDownloads = document.createElement('span');
  116. assetDownloads.id = 'grdcounter';
  117. assetDownloads.className = 'color-fg-muted text-sm-right ml-md-3';
  118. assetDownloads.textContent = `${asset.download_count} Downloads`;
  119. assetDownloads.style.whiteSpace = 'nowrap';
  120.  
  121. const fileSize = assetDataElem.firstElementChild;
  122. if (fileSize == null) continue;
  123. assetDataElem.insertBefore(assetDownloads, fileSize);
  124. fileSize.classList.remove('flex-auto');
  125. return true;
  126. }
  127. }
  128. return false;
  129. }
  130.  
  131. function initPAT() {
  132. if (GM_getValue("GITHUB_PAT") == undefined) GM_setValue("GITHUB_PAT", "");
  133. }
  134.  
  135. (function() {
  136. initPAT();
  137. initObserver();
  138. })();

QingJ © 2025

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