// ==UserScript==
// @name Grok Rate Limit Display
// @namespace http://tampermonkey.net/
// @version 2.6
// @description Displays Grok rate limit on screen based on selected model/mode
// @author Blankspeaker, Originally ported from CursedAtom's chrome extension
// @match https://grok.com/*
// @match https://grok.x.ai/*
// @match https://x.com/*
// @match https://twitter.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
console.log('Grok Rate Limit Script loaded');
// Variable to store the last known rate limit values
let lastRateLimit = { remainingQueries: null, totalQueries: null };
// Function to remove any existing rate limit display
function removeExistingRateLimit() {
const existing = document.getElementById('grok-rate-limit');
if (existing) {
existing.remove();
}
}
// Function to normalize model names (lowercase, hyphenated)
function normalizeModelName(modelName) {
return modelName.toLowerCase().replace(/\s+/g, '-');
}
// Function to update or inject the rate limit display
function updateRateLimitDisplay(queryBar, response) {
let rateLimitContainer = document.getElementById('grok-rate-limit');
// If no container exists, create and insert it
if (!rateLimitContainer) {
const bottomBar = queryBar.querySelector('.flex.gap-1\\.5.absolute.inset-x-0.bottom-0');
if (!bottomBar) {
console.log('Bottom bar not found');
return;
}
const attachButton = bottomBar.querySelector('button[aria-label="Attach"]');
if (!attachButton) {
console.log('Attach button not found');
return;
}
rateLimitContainer = document.createElement('div');
rateLimitContainer.id = 'grok-rate-limit';
rateLimitContainer.className = 'inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-60 disabled:cursor-not-allowed [&_svg]:duration-100 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:-mx-0.5 select-none border-border-l2 text-fg-primary hover:bg-button-ghost-hover disabled:hover:bg-transparent h-10 px-3.5 py-2 text-sm rounded-full group/rate-limit transition-colors duration-100 relative overflow-hidden border cursor-pointer';
rateLimitContainer.style.opacity = '0.8';
rateLimitContainer.style.transition = 'opacity 0.1s ease-in-out';
// Add click event to update on click
rateLimitContainer.addEventListener('click', () => {
fetchAndUpdateRateLimit(queryBar);
});
// Add a gauge SVG icon
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', '18');
svg.setAttribute('height', '18');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('fill', 'none');
svg.setAttribute('class', 'stroke-[2] text-fg-secondary group-hover/rate-limit:text-fg-primary transition-colors duration-100');
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('cx', '12');
circle.setAttribute('cy', '12');
circle.setAttribute('r', '8');
circle.setAttribute('stroke', 'currentColor');
circle.setAttribute('stroke-width', '2');
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', 'M12 12L12 6');
path.setAttribute('stroke', 'currentColor');
path.setAttribute('stroke-width', '2');
path.setAttribute('stroke-linecap', 'round');
svg.appendChild(circle);
svg.appendChild(path);
rateLimitContainer.appendChild(svg);
// Add a span for the text
const textSpan = document.createElement('span');
rateLimitContainer.appendChild(textSpan);
attachButton.insertAdjacentElement('afterend', rateLimitContainer);
}
// Update text based on response
const textSpan = rateLimitContainer.querySelector('span');
if (response.error) {
// If there's an error, use the last known rate limit values if available
if (lastRateLimit.remainingQueries !== null && lastRateLimit.totalQueries !== null) {
textSpan.textContent = `${lastRateLimit.remainingQueries}/${lastRateLimit.totalQueries}`;
} else {
// If no previous data exists, show a neutral message without "Error"
textSpan.textContent = 'Unavailable';
}
} else {
const { remainingQueries, totalQueries } = response;
// Store the latest rate limit values
lastRateLimit.remainingQueries = remainingQueries;
lastRateLimit.totalQueries = totalQueries;
textSpan.textContent = `${remainingQueries}/${totalQueries}`;
}
}
// Function to fetch and update rate limit
function fetchAndUpdateRateLimit(queryBar) {
if (!queryBar || !document.body.contains(queryBar)) {
console.log('Query bar not valid, skipping update');
return;
}
// Detect current model from the UI
let modelName = 'grok-4'; // Updated default to grok-4 as of 2025
const modelSpan = queryBar.querySelector('span.inline-block.text-primary');
if (modelSpan) {
const modelText = modelSpan.textContent.trim();
modelName = normalizeModelName(modelText);
}
// Determine requestKind based on model
let requestKind = 'DEFAULT';
if (modelName === 'grok-3') {
const thinkButton = queryBar.querySelector('button[aria-label="Think"]');
if (thinkButton && thinkButton.getAttribute('aria-pressed') === 'true') {
requestKind = 'REASONING';
} else {
let searchButton = queryBar.querySelector('button[aria-label^="Deep"]');
if (searchButton && searchButton.getAttribute('aria-pressed') === 'true') {
const modeSpan = searchButton.querySelector('span');
if (modeSpan) {
const modeText = modeSpan.textContent.trim();
if (modeText === 'DeepSearch') {
requestKind = 'DEEPSEARCH';
} else if (modeText === 'DeeperSearch') {
requestKind = 'DEEPERSEARCH';
}
}
}
}
}
fetch(window.location.origin + '/rest/rate-limits', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
requestKind: requestKind,
modelName: modelName,
}),
credentials: 'include',
})
.then((res) => {
if (!res.ok) {
if (res.status === 401 || res.status === 403) {
throw new Error('Authentication required: Please sign in.');
}
throw new Error(`HTTP error: Status ${res.status}`);
}
return res.json();
})
.then((data) => {
if (queryBar && document.body.contains(queryBar)) {
updateRateLimitDisplay(queryBar, data);
}
})
.catch((err) => {
if (queryBar && document.body.contains(queryBar)) {
updateRateLimitDisplay(queryBar, { error: err.message });
}
});
}
// Function to setup or cleanup Grok-3 specific observers
function setupGrok3Observers(queryBar, currentModel, lastThinkObserver, lastSearchObserver) {
if (currentModel === 'grok-3') {
if (!lastThinkObserver) {
const thinkButton = queryBar.querySelector('button[aria-label="Think"]');
if (thinkButton) {
lastThinkObserver = new MutationObserver(() => {
fetchAndUpdateRateLimit(queryBar);
});
lastThinkObserver.observe(thinkButton, { attributes: true, attributeFilter: ['aria-pressed', 'class'] });
}
}
if (!lastSearchObserver) {
const searchButton = queryBar.querySelector('button[aria-label^="Deep"]');
if (searchButton) {
lastSearchObserver = new MutationObserver(() => {
fetchAndUpdateRateLimit(queryBar);
});
lastSearchObserver.observe(searchButton, { attributes: true, attributeFilter: ['aria-pressed', 'class'], childList: true, subtree: true, characterData: true });
}
}
} else {
if (lastThinkObserver) {
lastThinkObserver.disconnect();
lastThinkObserver = null;
}
if (lastSearchObserver) {
lastSearchObserver.disconnect();
lastSearchObserver = null;
}
}
return { lastThinkObserver, lastSearchObserver };
}
// Function to observe the DOM for the query bar
function observeDOM() {
let lastQueryBar = null;
let lastModel = null;
let lastModelObserver = null;
let lastThinkObserver = null;
let lastSearchObserver = null;
let lastInputElement = null;
let lastSendButton = null;
// Initial check for query bar
const initialQueryBar = document.querySelector('.query-bar');
if (initialQueryBar) {
removeExistingRateLimit();
fetchAndUpdateRateLimit(initialQueryBar);
lastQueryBar = initialQueryBar;
const modelSpan = initialQueryBar.querySelector('span.inline-block.text-primary');
if (modelSpan) {
lastModel = normalizeModelName(modelSpan.textContent.trim());
lastModelObserver = new MutationObserver(() => {
const currentModel = normalizeModelName(modelSpan.textContent.trim());
if (currentModel !== lastModel) {
lastModel = currentModel;
fetchAndUpdateRateLimit(lastQueryBar);
// Setup or cleanup Grok-3 observers
const observers = setupGrok3Observers(lastQueryBar, currentModel, lastThinkObserver, lastSearchObserver);
lastThinkObserver = observers.lastThinkObserver;
lastSearchObserver = observers.lastSearchObserver;
}
});
lastModelObserver.observe(modelSpan, { characterData: true, childList: true, subtree: true });
}
// Initial setup for Grok-3 observers if applicable
const observers = setupGrok3Observers(initialQueryBar, lastModel, lastThinkObserver, lastSearchObserver);
lastThinkObserver = observers.lastThinkObserver;
lastSearchObserver = observers.lastSearchObserver;
// Set up event listeners for query submission
const inputElement = initialQueryBar.querySelector('textarea');
if (inputElement) {
lastInputElement = inputElement;
inputElement.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
setTimeout(() => fetchAndUpdateRateLimit(lastQueryBar), 5000);
}
});
}
const sendButton = initialQueryBar.querySelector('div.h-10.relative.aspect-square');
if (sendButton) {
lastSendButton = sendButton;
sendButton.addEventListener('click', () => {
setTimeout(() => fetchAndUpdateRateLimit(lastQueryBar), 5000);
});
}
}
const observer = new MutationObserver((mutations) => {
const queryBar = document.querySelector('.query-bar');
// Handle query bar changes
if (queryBar && queryBar !== lastQueryBar) {
removeExistingRateLimit();
fetchAndUpdateRateLimit(queryBar);
lastQueryBar = queryBar;
if (lastModelObserver) {
lastModelObserver.disconnect();
lastModelObserver = null;
}
if (lastThinkObserver) {
lastThinkObserver.disconnect();
lastThinkObserver = null;
}
if (lastSearchObserver) {
lastSearchObserver.disconnect();
lastSearchObserver = null;
}
const modelSpan = queryBar.querySelector('span.inline-block.text-primary');
if (modelSpan) {
lastModel = normalizeModelName(modelSpan.textContent.trim());
lastModelObserver = new MutationObserver(() => {
const currentModel = normalizeModelName(modelSpan.textContent.trim());
if (currentModel !== lastModel) {
lastModel = currentModel;
fetchAndUpdateRateLimit(lastQueryBar);
// Setup or cleanup Grok-3 observers
const observers = setupGrok3Observers(lastQueryBar, currentModel, lastThinkObserver, lastSearchObserver);
lastThinkObserver = observers.lastThinkObserver;
lastSearchObserver = observers.lastSearchObserver;
}
});
lastModelObserver.observe(modelSpan, { characterData: true, childList: true, subtree: true });
}
// Initial setup for Grok-3 observers if applicable
const observersInit = setupGrok3Observers(queryBar, lastModel, lastThinkObserver, lastSearchObserver);
lastThinkObserver = observersInit.lastThinkObserver;
lastSearchObserver = observersInit.lastSearchObserver;
// Set up event listeners for query submission
const inputElement = queryBar.querySelector('textarea');
if (inputElement && inputElement !== lastInputElement) {
lastInputElement = inputElement;
inputElement.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
setTimeout(() => fetchAndUpdateRateLimit(lastQueryBar), 5000);
}
});
}
const sendButton = queryBar.querySelector('div.h-10.relative.aspect-square');
if (sendButton && sendButton !== lastSendButton) {
lastSendButton = sendButton;
sendButton.addEventListener('click', () => {
setTimeout(() => fetchAndUpdateRateLimit(lastQueryBar), 5000);
});
}
} else if (!queryBar && lastQueryBar) {
removeExistingRateLimit();
if (lastModelObserver) {
lastModelObserver.disconnect();
lastModelObserver = null;
}
if (lastThinkObserver) {
lastThinkObserver.disconnect();
lastThinkObserver = null;
}
if (lastSearchObserver) {
lastSearchObserver.disconnect();
lastSearchObserver = null;
}
lastQueryBar = null;
lastModel = null;
lastInputElement = null;
lastSendButton = null;
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
// Polling every 5 minutes for rate limit updates when tab is focused
setInterval(() => {
if (lastQueryBar && document.hasFocus()) {
fetchAndUpdateRateLimit(lastQueryBar);
}
}, 300 * 1000);
}
// Start observing the DOM for changes
observeDOM();
})();