Github Find Active Forks

Find the most active forks of a GitHub repository.

  1. // ==UserScript==
  2. // @name Github Find Active Forks
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.5.0
  5. // @copyright 2025, Asriel (https://gf.qytechs.cn/de/users/1375984-asriel-aac)
  6. // @description Find the most active forks of a GitHub repository.
  7. // @author Asriel
  8. // @match *://github.com/*
  9. // @icon https://github.githubassets.com/favicons/favicon-dark.png
  10. // @grant GM_addStyle
  11. // @run-at document-end
  12. // @license MIT
  13. // @namespace https://gf.qytechs.cn/users/448067
  14. // ==/UserScript==
  15.  
  16. // Securely define CSS styles directly within the script
  17. GM_addStyle(`
  18. .table { width: 100%; border-collapse: collapse; }
  19. .table th, .table td { border: 1px solid #ddd; padding: 8px; text-align: left; }
  20. .table th { background-color: #f2f2f2; }
  21. .avatar { border-radius: 50%; }
  22. `);
  23.  
  24. // Load latest, more secure versions of external libraries
  25. const loadScript = (url) => {
  26. return new Promise((resolve) => {
  27. let script = document.createElement("script");
  28. script.src = url;
  29. script.onload = resolve;
  30. document.head.appendChild(script);
  31. });
  32. };
  33.  
  34. // Define secure versions of required libraries
  35. const scripts = [
  36. "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js",
  37. "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.0/js/bootstrap.bundle.min.js",
  38. "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js",
  39. "https://cdnjs.cloudflare.com/ajax/libs/jquery-footable/3.1.6/footable.min.js"
  40. ];
  41.  
  42. // Load scripts sequentially before executing main function
  43. Promise.all(scripts.map(loadScript)).then(() => {
  44.  
  45. const SIZE_KILO = 1024;
  46. const UNITS = ["Bytes", "KB", "MB", "GB", "TB", "PB"];
  47.  
  48. const fetchData = async (url) => {
  49. try {
  50. let response = await fetch(url);
  51. if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
  52. return await response.json();
  53. } catch (error) {
  54. console.error("Fetch error:", error);
  55. return null;
  56. }
  57. };
  58.  
  59. const getHumanFileSize = (size) => {
  60. if (size <= 0) return { size: "0", unit: UNITS[0] };
  61. size *= SIZE_KILO;
  62. const order = Math.floor(Math.log(size) / Math.log(SIZE_KILO));
  63. return { size: (size / Math.pow(SIZE_KILO, order)).toFixed(2), unit: UNITS[order] };
  64. };
  65.  
  66. const createTable = (rJson, cJson) => {
  67. const humanSize = getHumanFileSize(cJson?.size ?? -1);
  68. const rowsData = [{
  69. repoName: `<img src="${cJson.owner.avatar_url}" width="24" height="24" class="avatar">
  70. <a href="${cJson.html_url}">${cJson.full_name}</a>`,
  71. repoStars: cJson?.stargazers_count ?? -1,
  72. repoForks: cJson?.forks_count ?? -1,
  73. repoOpenIssue: cJson?.open_issues_count ?? -1,
  74. repoSize: humanSize,
  75. repoModified: Number(moment(cJson?.pushed_at ?? "NULL").format("x"))
  76. }];
  77.  
  78. rJson.forEach((v) => {
  79. const humanSize = getHumanFileSize(v?.size ?? -1);
  80. rowsData.push({
  81. repoName: `<img src="${v.owner.avatar_url}" width="24" height="24" class="avatar">
  82. <a href="${v.html_url}">${v.full_name}</a>`,
  83. repoStars: v?.stargazers_count ?? -1,
  84. repoForks: v?.forks_count ?? -1,
  85. repoOpenIssue: v?.open_issues_count ?? -1,
  86. repoSize: humanSize,
  87. repoModified: Number(moment(v?.pushed_at ?? "NULL").format("x"))
  88. });
  89. });
  90.  
  91. jQuery(() => {
  92. $(".table").footable({
  93. columns: [
  94. { name: "repoName", title: "Repo" },
  95. { name: "repoStars", title: "Stars", breakpoints: "xs", type: "number" },
  96. { name: "repoForks", title: "Forks", breakpoints: "xs", type: "number" },
  97. { name: "repoOpenIssue", title: "Open Issues", breakpoints: "xs", type: "number" },
  98. {
  99. name: "repoSize",
  100. title: "Size",
  101. breakpoints: "xs",
  102. type: "object",
  103. formatter: (value) => value ? `${value.size} ${value.unit}` : "-",
  104. sortValue: (value) => value ? value.size * Math.pow(SIZE_KILO, UNITS.indexOf(value.unit)) : 0
  105. },
  106. {
  107. name: "repoModified",
  108. title: "Last Push",
  109. type: "date",
  110. breakpoints: "xs sm md",
  111. formatter: (value) => moment().to(moment(value, "YYYYMMDD"))
  112. }
  113. ],
  114. rows: rowsData
  115. });
  116. });
  117. };
  118.  
  119. const loadMain = async () => {
  120. const pathComponents = window.location.pathname.split("/");
  121. if (pathComponents.length >= 3) {
  122. const user = pathComponents[1];
  123. const repo = pathComponents[2];
  124. const divForks = document.querySelector('div[id="network"]');
  125. if (!divForks) return;
  126.  
  127. divForks.innerHTML = `<table class="table" data-paging="true" data-sorting="true"></table>`;
  128. const repoData = await fetchData(`https://api.github.com/repos/${user}/${repo}`);
  129. const forksData = await fetchData(`https://api.github.com/repos/${user}/${repo}/forks?sort=newest&per_page=100`);
  130. if (repoData && forksData) createTable(forksData, repoData);
  131. }
  132. };
  133.  
  134. document.addEventListener("turbo:render", () => {
  135. if (location.pathname.endsWith("/network/members")) loadMain();
  136. });
  137.  
  138. if (location.pathname.endsWith("/network/members")) loadMain();
  139.  
  140. });

QingJ © 2025

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