// ==UserScript==
// @name Grok ratelimit indicator
// @namespace https://6942020.xyz/
// @version 1.3
// @description Shows ratelimit information on Grok
// @author WadeGrimridge
// @match https://grok.com/*
// @license MIT
// @grant none
// ==/UserScript==
(function () {
"use strict";
const CONFIG = {
MAX_RETRIES: 10,
RETRY_DELAY: 1000,
RATE_LIMIT_ENDPOINT: "/rest/rate-limits",
REQUEST_KINDS: ["DEFAULT", "REASONING", "DEEPSEARCH", "DEEPERSEARCH"],
MODELS: {
"grok-3": {
DEFAULT: "Grok 3",
REASONING: "Think",
DEEPSEARCH: "DeepSearch",
DEEPERSEARCH: "DeeperSearch",
},
},
};
const state = {
rateInfoElement: null,
modelRateLimits: {
"grok-3": {
DEFAULT: null,
REASONING: null,
DEEPSEARCH: null,
DEEPERSEARCH: null,
},
},
};
const formatTime = (seconds) => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
if (hours > 0) {
return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
}
return remainingSeconds > 0
? `${minutes}m ${remainingSeconds}s`
: `${minutes}m`;
};
const isValidRateData = (data) =>
data &&
typeof data.remainingQueries === "number" &&
typeof data.totalQueries === "number" &&
typeof data.windowSizeSeconds === "number";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const fetchRateLimit = async (
modelName,
requestKind = "DEFAULT",
attempt = 1
) => {
try {
const response = await fetch(CONFIG.RATE_LIMIT_ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ requestKind, modelName }),
});
if (response.status !== 200 && attempt <= CONFIG.MAX_RETRIES) {
await sleep(CONFIG.RETRY_DELAY);
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 > CONFIG.MAX_RETRIES && state.rateInfoElement) {
state.rateInfoElement.textContent = "Couldn't fetch ratelimit info";
}
}
};
const formatRateLimitLine = (data, displayName) => {
const timeStr = formatTime(data.windowSizeSeconds);
return `${displayName}: ${data.remainingQueries}/${data.totalQueries} (${timeStr})`;
};
const updateRateInfo = (data, modelName, requestKind = "DEFAULT") => {
if (!state.rateInfoElement) return;
state.modelRateLimits[modelName][requestKind] = data;
const lines = [];
CONFIG.REQUEST_KINDS.forEach((kind) => {
const modelData = state.modelRateLimits["grok-3"][kind];
if (modelData) {
lines.push(
formatRateLimitLine(modelData, CONFIG.MODELS["grok-3"][kind])
);
}
});
state.rateInfoElement.textContent = lines.join(" | ");
};
const interceptFetch = () => {
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(CONFIG.RATE_LIMIT_ENDPOINT)) {
return originalFetch.apply(this, args);
}
const response = await originalFetch.apply(this, args);
const { modelName, requestKind } = JSON.parse(options.body);
const clone = response.clone();
clone.json().then((data) => {
if (isValidRateData(data)) {
updateRateInfo(data, modelName, requestKind);
}
});
return response;
};
};
const createRateInfoElement = () => {
const targetDiv = document.querySelector(
'main div:has(> a[aria-label="Home page"])'
);
if (!targetDiv || state.rateInfoElement) return;
const headerDiv = targetDiv.parentElement;
headerDiv.classList.remove(
"@[80rem]/nav:h-0",
"@[80rem]/nav:top-8",
"@[80rem]/nav:from-transparent",
"@[80rem]/nav:via-transparent"
);
state.rateInfoElement = document.createElement("div");
state.rateInfoElement.className = "ml-2 text-sm break-words";
state.rateInfoElement.style.maxWidth = "calc(100vw - 240px)";
state.rateInfoElement.textContent = "Fetching ratelimit info...";
targetDiv.appendChild(state.rateInfoElement);
initializeRateLimits();
};
const initializeRateLimits = async () => {
await fetchRateLimit("grok-3", "DEFAULT");
for (const kind of CONFIG.REQUEST_KINDS.slice(1)) {
await sleep(100);
await fetchRateLimit("grok-3", kind);
}
};
const waitForElement = () => {
const targetDiv = document.querySelector(
'main div:has(> a[aria-label="Home page"])'
);
if (targetDiv) {
createRateInfoElement();
} else {
requestAnimationFrame(waitForElement);
}
};
interceptFetch();
waitForElement();
})();