// ==UserScript==
// @name Grok+
// @namespace https://6942020.xyz/
// @version 1.4.1
// @description Adds back Grok 2 and shows rate limits
// @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-2": { displayName: "Grok 2" },
"grok-3": {
DEFAULT: "Grok 3",
REASONING: "Think",
DEEPSEARCH: "DeepSearch",
DEEPERSEARCH: "DeeperSearch",
},
},
};
const state = {
rateInfoElement: null,
selectedModel: "grok-3",
modelRateLimits: {
"grok-2": null,
"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;
if (modelName === "grok-3") {
state.modelRateLimits[modelName][requestKind] = data;
} else {
state.modelRateLimits[modelName] = 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])
);
}
});
const grok2Data = state.modelRateLimits["grok-2"];
if (grok2Data) {
lines.push(
formatRateLimitLine(grok2Data, CONFIG.MODELS["grok-2"].displayName)
);
}
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();
const isChatUrl = (url) => {
return (
(url.includes("/rest/app-chat/conversations/") &&
url.endsWith("/responses")) ||
url === "https://grok.com/rest/app-chat/conversations/new"
);
};
if (options?.method === "POST" && isChatUrl(url)) {
try {
const body = JSON.parse(options.body);
if (body.modelName && state.selectedModel === "grok-2") {
const newOptions = { ...options };
body.modelName = "grok-2";
newOptions.body = JSON.stringify(body);
args[1] = newOptions;
}
} catch {}
}
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);
}
await sleep(100);
await fetchRateLimit("grok-2");
};
const waitForElement = () => {
const targetDiv = document.querySelector(
'main div:has(> a[aria-label="Home page"])'
);
if (targetDiv) {
createRateInfoElement();
} else {
requestAnimationFrame(waitForElement);
}
};
const createModelPickerOverlay = () => {
if (document.getElementById("model-picker-overlay")) return;
const overlay = document.createElement("div");
overlay.id = "model-picker-overlay";
Object.assign(overlay.style, {
position: "fixed",
bottom: "16px",
right: "16px",
backgroundColor: "white",
border: "1px solid #ccc",
borderRadius: "8px",
padding: "8px",
display: "flex",
gap: "8px",
zIndex: "10000",
fontSize: "14px",
});
const makeButton = (model, label) => {
const btn = document.createElement("button");
btn.textContent = label;
btn.dataset.model = model;
btn.style.padding = "4px 8px";
btn.style.cursor = "pointer";
btn.style.border = "1px solid #888";
btn.style.borderRadius = "4px";
btn.style.backgroundColor =
state.selectedModel === model ? "#ddd" : "white";
btn.addEventListener("click", () => {
state.selectedModel = model;
overlay.querySelectorAll("button").forEach((b) => {
b.style.backgroundColor =
b.dataset.model === model ? "#ddd" : "white";
});
});
return btn;
};
overlay.appendChild(makeButton("grok-3", "Grok 3"));
overlay.appendChild(makeButton("grok-2", "Grok 2"));
document.body.appendChild(overlay);
};
interceptFetch();
waitForElement();
createModelPickerOverlay();
})();