Grok ratelimit indicator

Shows ratelimit information on Grok

// ==UserScript==
// @name         Grok ratelimit indicator
// @namespace    https://6942020.xyz/
// @version      1.2
// @description  Shows ratelimit information on Grok
// @author       WadeGrimridge
// @match        https://grok.com/*
// @license      MIT
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  let rateInfoElement = null;
  const modelRateLimits = {
    "grok-latest": null,
    "grok-3": {
      DEFAULT: null,
      REASONING: null,
      DEEPSEARCH: null,
    },
  };
  const modelDisplayNames = {
    "grok-latest": "Grok 2",
    "grok-3": {
      DEFAULT: "Grok 3",
      REASONING: "Think",
      DEEPSEARCH: "DeepSearch",
      DEEPERSEARCH: "DeeperSearch",
    },
  };

  function formatTime(seconds) {
    if (seconds >= 3600) {
      const hours = Math.floor(seconds / 3600);
      const remainingMinutes = Math.floor((seconds % 3600) / 60);
      return remainingMinutes > 0
        ? `${hours}h ${remainingMinutes}m`
        : `${hours}h`;
    }
    const minutes = Math.floor(seconds / 60);
    const remainingSeconds = seconds % 60;
    return remainingSeconds > 0
      ? `${minutes}m ${remainingSeconds}s`
      : `${minutes}m`;
  }

  async function fetchRateLimit(
    modelName,
    requestKind = "DEFAULT",
    attempt = 1
  ) {
    try {
      const response = await fetch("/rest/rate-limits", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ requestKind, modelName }),
      });

      if (response.status !== 200 && attempt <= 10) {
        await new Promise((resolve) => setTimeout(resolve, 1000));
        return fetchRateLimit(modelName, requestKind, attempt + 1);
      }

      const data = await response.json();
      if (!isValidRateData(data)) return;

      updateRateInfo(data, modelName, requestKind);
    } catch (error) {
      console.error(`[grok-ratelimit] Rate limit fetch failed:`, error);
      if (attempt <= 10) return;
      if (rateInfoElement) {
        rateInfoElement.textContent = "Couldn't fetch ratelimit info";
      }
    }
  }

  function isValidRateData(data) {
    return (
      data &&
      typeof data.remainingQueries === "number" &&
      typeof data.totalQueries === "number"
    );
  }

  function updateRateInfo(data, modelName, requestKind = "DEFAULT") {
    if (!rateInfoElement) return;

    if (modelName === "grok-3") {
      modelRateLimits[modelName][requestKind] = data;
    } else {
      modelRateLimits[modelName] = data;
    }

    const lines = [];

    for (const kind of ["DEFAULT", "REASONING", "DEEPSEARCH", "DEEPERSEARCH"]) {
      const modelData = modelRateLimits["grok-3"][kind];
      if (!modelData) continue;

      const timeStr = formatTime(modelData.windowSizeSeconds);
      const displayName = modelDisplayNames["grok-3"][kind];
      lines.push(
        `${displayName}: ${modelData.remainingQueries}/${modelData.totalQueries} (${timeStr})`
      );
    }

    const grok2Data = modelRateLimits["grok-latest"];
    if (grok2Data) {
      const timeStr = formatTime(grok2Data.windowSizeSeconds);
      lines.push(
        `${modelDisplayNames["grok-latest"]}: ${grok2Data.remainingQueries}/${grok2Data.totalQueries} (${timeStr})`
      );
    }

    rateInfoElement.textContent = lines.join(" | ");
  }

  const originalFetch = window.fetch;
  window.fetch = async function (...args) {
    const [resource, options] = args;
    const url =
      resource instanceof Request ? resource.url : resource.toString();

    if (!url.includes("/rest/rate-limits")) {
      return originalFetch.apply(this, args);
    }

    const requestBody = JSON.parse(options.body);
    const modelName = requestBody.modelName;
    const requestKind = requestBody.requestKind;
    const response = await originalFetch.apply(this, args);

    const clone = response.clone();
    clone.json().then((data) => {
      if (!isValidRateData(data)) return;
      updateRateInfo(data, modelName, requestKind);
    });

    return response;
  };

  function createRateInfoElement() {
    const targetDiv = document.querySelector(
      'main div:has(> a[aria-label="Home page"])'
    );
    if (targetDiv && !rateInfoElement) {
      const headerDiv = targetDiv.parentElement;

      headerDiv.classList.remove(
        "@[80rem]/nav:h-0",
        "@[80rem]/nav:top-8",
        "@[80rem]/nav:from-transparent",
        "@[80rem]/nav:via-transparent"
      );

      rateInfoElement = document.createElement("div");
      rateInfoElement.className = "ml-2 text-sm break-words";
      rateInfoElement.style.maxWidth = "calc(100vw - 240px)";
      rateInfoElement.textContent = "Fetching ratelimit info...";
      targetDiv.appendChild(rateInfoElement);

      const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

      (async () => {
        await fetchRateLimit("grok-3", "DEFAULT");
        await sleep(100);
        await fetchRateLimit("grok-3", "REASONING");
        await sleep(100);
        await fetchRateLimit("grok-3", "DEEPSEARCH");
        await sleep(100);
        await fetchRateLimit("grok-3", "DEEPERSEARCH");
        await sleep(100);
        await fetchRateLimit("grok-latest");
      })();
    }
  }

  function waitForElement() {
    const targetDiv = document.querySelector(
      'main div:has(> a[aria-label="Home page"])'
    );
    if (targetDiv) {
      createRateInfoElement();
    } else {
      requestAnimationFrame(waitForElement);
    }
  }

  waitForElement();
})();

QingJ © 2025

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