Grok 3 Rate Limit Display

Displays Grok 3 rate limit (Queries left: Remaining of Total)

As of 25.04.2025. See ბოლო ვერსია.

// ==UserScript==
// @name         Grok 3 Rate Limit Display
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Displays Grok 3 rate limit (Queries left: Remaining of Total) 
// @author       Blankspeaker (based on the chrome extension by CursedAtom) & Grok
// @match        https://grok.com/chat/*
// @grant        none
// @license      GNU GPLv3
// ==/UserScript==

/*
 * DEBUG_MODE: Set to true for detailed logs (e.g., polling, span creation, click events).
 * Set to false for minimal logs (only errors). Default: false.
 */
(function() {
    'use strict';

    const DEBUG_MODE = false; // Set to true for troubleshooting, false for minimal output

    // Function to log messages based on debug mode
    function log(message) {
        if (DEBUG_MODE) {
            console.log(`[Grok Rate Limit] ${message}`);
        }
    }

    // Function to fetch rate limit
    async function fetchRateLimit() {
        try {
            const response = await fetch('https://grok.com/rest/rate-limits', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    requestKind: 'DEFAULT',
                    modelName: 'grok-3',
                }),
                credentials: 'include',
            });

            if (!response.ok) {
                throw new Error(`HTTP error: ${response.status}`);
            }

            const data = await response.json();
            log('Rate limit data: ' + JSON.stringify(data));
            return data;
        } catch (error) {
            console.error('[Grok Rate Limit] Failed to fetch rate limit:', error);
            return null;
        }
    }

    // Function to update or create the rate limit display
    async function displayRateLimit() {
        try {
            // Check if rate limit span already exists
            const existingSpan = document.querySelector('span.rate-limit-span');
            if (existingSpan) {
                // Update existing span
                const rateLimitData = await fetchRateLimit();
                if (rateLimitData) {
                    const remaining = rateLimitData.remainingQueries ?? 'Unknown';
                    const total = rateLimitData.totalQueries ?? 'Unknown';
                    existingSpan.textContent = `Queries left: ${remaining} of ${total} `;
                }
                log('Rate limit span updated');
                return true; // Indicate span exists
            }

            // Try primary target: <a> with href="/chat"
            let targetLink = document.querySelector('a.inline-flex.items-center.justify-center.gap-2[href="/chat"]');
            if (!targetLink) {
                // Fall back to alternative target: <a> with href="/chat#private"
                targetLink = document.querySelector('a.inline-flex.items-center.justify-center.gap-2[href="/chat#private"][aria-label="Switch to Private Chat"]');
                if (!targetLink) {
                    log('Both target chat links not found');
                    return false; // Indicate failure to find target
                }
            }

            // Fetch rate limit data
            const rateLimitData = await fetchRateLimit();
            if (!rateLimitData) {
                return false;
            }

            // Extract rate limit info
            const remaining = rateLimitData.remainingQueries ?? 'Unknown';
            const total = rateLimitData.totalQueries ?? 'Unknown';

            // Create rate limit display element
            const rateLimitSpan = document.createElement('span');
            rateLimitSpan.textContent = `Queries left: ${remaining} of ${total} `;
            rateLimitSpan.classList.add('rate-limit-span');
            rateLimitSpan.style.marginRight = '8px';
            rateLimitSpan.style.color = '#666';
            rateLimitSpan.style.fontSize = '12px';
            rateLimitSpan.style.display = 'inline-flex';
            rateLimitSpan.style.alignItems = 'center';

            // Insert to the left of the target link
            targetLink.parentNode.insertBefore(rateLimitSpan, targetLink);
            log('Rate limit span created');
            return true; // Indicate success
        } catch (error) {
            console.error('[Grok Rate Limit] Error in displayRateLimit:', error);
            return false;
        }
    }

    // Function to poll for either target element with retries
    async function pollForTarget(maxAttempts = 3, interval = 3000) {
        try {
            let attempts = 0;
            while (attempts < maxAttempts) {
                // Early exit if span already exists
                if (document.querySelector('span.rate-limit-span')) {
                    log('Rate limit span already exists, stopping polling');
                    return;
                }
                const success = await displayRateLimit();
                if (success) {
                    return; // Exit once displayed
                }
                attempts++;
                await new Promise(resolve => setTimeout(resolve, interval));
            }
            log('Max attempts reached, could not find either target chat link');
        } catch (error) {
            console.error('[Grok Rate Limit] Error in pollForTarget:', error);
        }
    }

    // Run initially with a delay to allow page to load
    try {
        setTimeout(() => {
            pollForTarget();
        }, 2000); // Start after 2 seconds
    } catch (error) {
        console.error('[Grok Rate Limit] Error setting up delayed polling:', error);
    }

    // Periodically refresh rate limit (every 120 seconds)
    try {
        setInterval(async () => {
            const existingSpan = document.querySelector('span.rate-limit-span');
            if (existingSpan) {
                const rateLimitData = await fetchRateLimit();
                if (rateLimitData) {
                    const remaining = rateLimitData.remainingQueries ?? 'Unknown';
                    const total = rateLimitData.totalQueries ?? 'Unknown';
                    existingSpan.textContent = `Queries left: ${remaining} of ${total} `;
                    log('Rate limit span refreshed');
                }
            }
        }, 120000); // Refresh every 2 minutes
    } catch (error) {
        console.error('[Grok Rate Limit] Error setting up periodic refresh:', error);
    }

    // Add click listener for the send button SVG to refresh rate limit after 1 second
    try {
        document.addEventListener('click', async (event) => {
            const svg = event.target.closest('svg[width="20"][height="20"][viewBox="0 0 24 24"][fill="none"][class="stroke-[2] relative"]');
            if (svg && svg.querySelector('path[d="M5 11L12 4M12 4L19 11M12 4V21"]')) {
                log('Send button SVG clicked, scheduling refresh');
                setTimeout(async () => {
                    const existingSpan = document.querySelector('span.rate-limit-span');
                    if (existingSpan) {
                        const rateLimitData = await fetchRateLimit();
                        if (rateLimitData) {
                            const remaining = rateLimitData.remainingQueries ?? 'Unknown';
                            const total = rateLimitData.totalQueries ?? 'Unknown';
                            existingSpan.textContent = `Queries left: ${remaining} of ${total} `;
                            log('Rate limit span refreshed after click');
                        }
                    } else {
                        // If no span exists, trigger full display logic
                        await displayRateLimit();
                    }
                }, 1000); // Refresh after 1 second
            }
        });
    } catch (error) {
        console.error('[Grok Rate Limit] Error setting up click listener:', error);
    }
})();