Github Find Active Forks

Find the most active forks of a GitHub repository.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Github Find Active Forks
// @namespace    http://tampermonkey.net/
// @version      1.5.0
// @copyright    2025, Asriel (https://greatest.deepsurf.us/de/users/1375984-asriel-aac)
// @description  Find the most active forks of a GitHub repository.
// @author       Asriel
// @match        *://github.com/*
// @icon         https://github.githubassets.com/favicons/favicon-dark.png
// @grant        GM_addStyle
// @run-at       document-end
// @license MIT
// @namespace https://greatest.deepsurf.us/users/448067
// ==/UserScript==

// Securely define CSS styles directly within the script
GM_addStyle(`
  .table { width: 100%; border-collapse: collapse; }
  .table th, .table td { border: 1px solid #ddd; padding: 8px; text-align: left; }
  .table th { background-color: #f2f2f2; }
  .avatar { border-radius: 50%; }
`);

// Load latest, more secure versions of external libraries
const loadScript = (url) => {
  return new Promise((resolve) => {
    let script = document.createElement("script");
    script.src = url;
    script.onload = resolve;
    document.head.appendChild(script);
  });
};

// Define secure versions of required libraries
const scripts = [
  "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js",
  "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.0/js/bootstrap.bundle.min.js",
  "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js",
  "https://cdnjs.cloudflare.com/ajax/libs/jquery-footable/3.1.6/footable.min.js"
];

// Load scripts sequentially before executing main function
Promise.all(scripts.map(loadScript)).then(() => {

  const SIZE_KILO = 1024;
  const UNITS = ["Bytes", "KB", "MB", "GB", "TB", "PB"];

  const fetchData = async (url) => {
    try {
      let response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
      return await response.json();
    } catch (error) {
      console.error("Fetch error:", error);
      return null;
    }
  };

  const getHumanFileSize = (size) => {
    if (size <= 0) return { size: "0", unit: UNITS[0] };
    size *= SIZE_KILO;
    const order = Math.floor(Math.log(size) / Math.log(SIZE_KILO));
    return { size: (size / Math.pow(SIZE_KILO, order)).toFixed(2), unit: UNITS[order] };
  };

  const createTable = (rJson, cJson) => {
    const humanSize = getHumanFileSize(cJson?.size ?? -1);
    const rowsData = [{
      repoName: `<img src="${cJson.owner.avatar_url}" width="24" height="24" class="avatar">
                 <a href="${cJson.html_url}">${cJson.full_name}</a>`,
      repoStars: cJson?.stargazers_count ?? -1,
      repoForks: cJson?.forks_count ?? -1,
      repoOpenIssue: cJson?.open_issues_count ?? -1,
      repoSize: humanSize,
      repoModified: Number(moment(cJson?.pushed_at ?? "NULL").format("x"))
    }];

    rJson.forEach((v) => {
      const humanSize = getHumanFileSize(v?.size ?? -1);
      rowsData.push({
        repoName: `<img src="${v.owner.avatar_url}" width="24" height="24" class="avatar">
                   <a href="${v.html_url}">${v.full_name}</a>`,
        repoStars: v?.stargazers_count ?? -1,
        repoForks: v?.forks_count ?? -1,
        repoOpenIssue: v?.open_issues_count ?? -1,
        repoSize: humanSize,
        repoModified: Number(moment(v?.pushed_at ?? "NULL").format("x"))
      });
    });

    jQuery(() => {
      $(".table").footable({
        columns: [
          { name: "repoName", title: "Repo" },
          { name: "repoStars", title: "Stars", breakpoints: "xs", type: "number" },
          { name: "repoForks", title: "Forks", breakpoints: "xs", type: "number" },
          { name: "repoOpenIssue", title: "Open Issues", breakpoints: "xs", type: "number" },
          {
            name: "repoSize",
            title: "Size",
            breakpoints: "xs",
            type: "object",
            formatter: (value) => value ? `${value.size} ${value.unit}` : "-",
            sortValue: (value) => value ? value.size * Math.pow(SIZE_KILO, UNITS.indexOf(value.unit)) : 0
          },
          {
            name: "repoModified",
            title: "Last Push",
            type: "date",
            breakpoints: "xs sm md",
            formatter: (value) => moment().to(moment(value, "YYYYMMDD"))
          }
        ],
        rows: rowsData
      });
    });
  };

  const loadMain = async () => {
    const pathComponents = window.location.pathname.split("/");
    if (pathComponents.length >= 3) {
      const user = pathComponents[1];
      const repo = pathComponents[2];
      const divForks = document.querySelector('div[id="network"]');
      if (!divForks) return;

      divForks.innerHTML = `<table class="table" data-paging="true" data-sorting="true"></table>`;
      const repoData = await fetchData(`https://api.github.com/repos/${user}/${repo}`);
      const forksData = await fetchData(`https://api.github.com/repos/${user}/${repo}/forks?sort=newest&per_page=100`);
      if (repoData && forksData) createTable(forksData, repoData);
    }
  };

  document.addEventListener("turbo:render", () => {
    if (location.pathname.endsWith("/network/members")) loadMain();
  });

  if (location.pathname.endsWith("/network/members")) loadMain();

});