Github Find Active Forks

Find the most active forks of a GitHub repository.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Github Find Active Forks
// @namespace    http://tampermonkey.net/
// @version      1.5.0
// @copyright    2025, Asriel (https://greasyfork.org/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://greasyfork.org/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();

});