Grok Quota Display

Displays rate limits on grok.com

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Grok Quota Display
// @namespace    nisc
// @version      2025.08.21-A
// @description  Displays rate limits on grok.com
// @homepageURL  https://github.com/nisc/grok-userscripts/
// @author       nisc
// @match        https://grok.com/*
// @icon         https://grok.com/images/favicon-light.png
// @run-at       document-end
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    /**
     * Configuration object containing all constants used in the script
     * Organized into logical sections for different aspects of the application
     */
    const CONFIG = {
        MODEL: {
            modelName: 'grok-4',
            requestKind: 'DEFAULT'
        },
        API: {
            endpoint: 'https://grok.com/rest/rate-limits',
            headers: {
                'Content-Type': 'application/json',
                'Accept-Language': 'en-US,en;q=0.9',
                'Accept': '*/*',
                'Origin': 'https://grok.com',
                'Sec-Fetch-Site': 'same-origin',
                'Sec-Fetch-Mode': 'cors',
                'Sec-Fetch-Dest': 'empty',
                'Referer': 'https://grok.com/',
                'Accept-Encoding': 'gzip, deflate, br',
                'Priority': 'u=1, i'
            }
        },
        UI: {
            styles: `
                .grok-rate-limit-wrapper {
                    display: flex;
                    flex-direction: column;
                    gap: 0.25rem;
                    padding: 0.5rem;
                    position: fixed;
                    bottom: 1rem;
                    right: 1rem;
                    z-index: 1000;
                }
                .grok-rate-limit-wrapper .grok-menu {
                    display: flex;
                    flex-direction: column;
                    gap: 0.25rem;
                }
                .grok-rate-limit-wrapper .grok-column {
                    display: flex;
                    flex-direction: column;
                    gap: 0.25rem;
                }
                .grok-rate-limit-wrapper .grok-rate-limit {
                    font-size: 0.75rem;
                    color: inherit;
                    font-family: inherit;
                    line-height: inherit;
                }
            `,
            refreshInterval: 10000 // in milliseconds
        },
        TIME: {
            SECONDS_PER_HOUR: 3600,
            SECONDS_PER_DAY: 86400
        }
    };

    /**
     * Utility functions for common operations
     * - ID generation for API requests
     * - Time window and value formatting
     */
    const utils = {
        // Generates a random ID for API request tracking
        generateId: () => Math.random().toString(16).slice(2),

        // Formats time windows into days or hours with appropriate units
        formatTimeWindow: seconds => {
            if (seconds >= CONFIG.TIME.SECONDS_PER_DAY) {
                const value = seconds / CONFIG.TIME.SECONDS_PER_DAY;
                return { value, unit: 'd' };
            }
            const value = seconds / CONFIG.TIME.SECONDS_PER_HOUR;
            return { value, unit: 'h' };
        },

        // Formats numeric values, rounding integers and fixing decimals to 1 place
        formatValue: value => Number.isInteger(value) ? Math.round(value) : value.toFixed(1)
    };

    /**
     * UI-related functions for creating and updating the display
     * Handles all DOM manipulation and styling
     */
    const ui = {
        // Injects custom styles into the document
        createStyles: () => {
            const style = document.createElement('style');
            style.textContent = CONFIG.UI.styles;
            document.head.appendChild(style);
        },

        // Creates the rate limit display menu structure
        createMenu: () => {
            const wrapper = document.createElement('div');
            wrapper.className = 'grok-rate-limit-wrapper';

            const menu = document.createElement('div');
            menu.id = 'grok-switcher-menu';
            menu.className = 'grok-menu';

            const column = document.createElement('div');
            column.className = 'grok-column';

            const div = document.createElement('div');
            div.id = 'rate_limit_tokens';
            div.className = 'grok-rate-limit';
            div.textContent = 'Tokens: N/A';
            column.appendChild(div);

            menu.appendChild(column);
            wrapper.appendChild(menu);
            return wrapper;
        },

        // Updates the display with new rate limit information
        updateRateLimits: limits => {
            const elem = document.getElementById('rate_limit_tokens');

            if (!elem) {
                return;
            }

            const windowSeconds = limits?.windowSizeSeconds;
            const remainingTokens = limits?.remainingTokens;
            const totalTokens = limits?.totalTokens;

            if (typeof windowSeconds === 'number' && typeof remainingTokens === 'number' && typeof totalTokens === 'number') {
                const { value, unit } = utils.formatTimeWindow(windowSeconds);
                const formattedValue = utils.formatValue(value);
                const display = `Tokens: <b>${remainingTokens}</b>/${totalTokens} (${formattedValue}${unit})`;
                elem.innerHTML = display;
            } else {
                elem.textContent = 'Tokens: N/A';
            }
        }
    };

    /**
     * API-related functions for fetching rate limits
     * Handles all server communication and error handling
     */
    const api = {
        // Fetches rate limits
        async fetchRateLimits() {
            try {
                const headers = {
                    ...CONFIG.API.headers,
                    'User-Agent': navigator.userAgent,
                    'X-Xai-Request-Id': utils.generateId()
                };

                const response = await fetch(CONFIG.API.endpoint, {
                    method: 'POST',
                    headers,
                    body: JSON.stringify({
                        modelName: CONFIG.MODEL.modelName,
                        requestKind: CONFIG.MODEL.requestKind
                    })
                });

                if (!response.ok) {
                    throw new Error(`Failed to fetch ${CONFIG.MODEL.modelName} rate limits`);
                }

                const json = await response.json();
                ui.updateRateLimits(json);
            } catch (error) {
                ui.updateRateLimits(null);
                console.error('GROK-USR: Failed to fetch rate limits. Please try again later.');
            }
        }
    };

    /**
     * Initializes the application:
     * 1. Creates and injects styles
     * 2. Creates and adds the display menu
     * 3. Fetches initial rate limit
     * 4. Sets up periodic updates
     */
    const init = () => {
        ui.createStyles();
        document.body.appendChild(ui.createMenu());
        api.fetchRateLimits();
        setInterval(api.fetchRateLimits, CONFIG.UI.refreshInterval);
    };

    // Initialize when DOM is ready
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        init();
    } else {
        document.addEventListener('DOMContentLoaded', init);
    }
})();