Github Find Active Forks

Find the most active forks of a GitHub repository.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

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.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

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

});