Grok 3 Rate Limit Display

Displays rate limit (Queries left: Remaining of Total) on Grok.com

As of 2025-04-25. See the latest version.

  1. // ==UserScript==
  2. // @name Grok 3 Rate Limit Display
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Displays rate limit (Queries left: Remaining of Total) on Grok.com
  6. // @author Blankspeaker (based on the chrome extension by CursedAtom) & Grok
  7. // @match https://grok.com/chat/*
  8. // @grant none
  9. // @license GNU GPLv3
  10. // ==/UserScript==
  11.  
  12. /*
  13. * DEBUG_MODE: Set to true for detailed logs (e.g., polling, span creation, click events).
  14. * Set to false for minimal logs (only errors). Default: false.
  15. */
  16. (function() {
  17. 'use strict';
  18.  
  19. const DEBUG_MODE = false; // Set to true for troubleshooting, false for minimal output
  20.  
  21. // Function to log messages based on debug mode
  22. function log(message) {
  23. if (DEBUG_MODE) {
  24. console.log(`[Grok Rate Limit] ${message}`);
  25. }
  26. }
  27.  
  28. // Function to fetch rate limit
  29. async function fetchRateLimit() {
  30. try {
  31. const response = await fetch('https://grok.com/rest/rate-limits', {
  32. method: 'POST',
  33. headers: {
  34. 'Content-Type': 'application/json',
  35. },
  36. body: JSON.stringify({
  37. requestKind: 'DEFAULT',
  38. modelName: 'grok-3',
  39. }),
  40. credentials: 'include',
  41. });
  42.  
  43. if (!response.ok) {
  44. throw new Error(`HTTP error: ${response.status}`);
  45. }
  46.  
  47. const data = await response.json();
  48. log('Rate limit data: ' + JSON.stringify(data));
  49. return data;
  50. } catch (error) {
  51. console.error('[Grok Rate Limit] Failed to fetch rate limit:', error);
  52. return null;
  53. }
  54. }
  55.  
  56. // Function to update or create the rate limit display
  57. async function displayRateLimit() {
  58. try {
  59. // Check if rate limit span already exists
  60. const existingSpan = document.querySelector('span.rate-limit-span');
  61. if (existingSpan) {
  62. // Update existing span
  63. const rateLimitData = await fetchRateLimit();
  64. if (rateLimitData) {
  65. const remaining = rateLimitData.remainingQueries ?? 'Unknown';
  66. const total = rateLimitData.totalQueries ?? 'Unknown';
  67. existingSpan.textContent = `Queries left: ${remaining} of ${total} `;
  68. }
  69. log('Rate limit span updated');
  70. return true; // Indicate span exists
  71. }
  72.  
  73. // Try primary target: <a> with href="/chat"
  74. let targetLink = document.querySelector('a.inline-flex.items-center.justify-center.gap-2[href="/chat"]');
  75. if (!targetLink) {
  76. // Fall back to alternative target: <a> with href="/chat#private"
  77. targetLink = document.querySelector('a.inline-flex.items-center.justify-center.gap-2[href="/chat#private"][aria-label="Switch to Private Chat"]');
  78. if (!targetLink) {
  79. log('Both target chat links not found');
  80. return false; // Indicate failure to find target
  81. }
  82. }
  83.  
  84. // Fetch rate limit data
  85. const rateLimitData = await fetchRateLimit();
  86. if (!rateLimitData) {
  87. return false;
  88. }
  89.  
  90. // Extract rate limit info
  91. const remaining = rateLimitData.remainingQueries ?? 'Unknown';
  92. const total = rateLimitData.totalQueries ?? 'Unknown';
  93.  
  94. // Create rate limit display element
  95. const rateLimitSpan = document.createElement('span');
  96. rateLimitSpan.textContent = `Queries left: ${remaining} of ${total} `;
  97. rateLimitSpan.classList.add('rate-limit-span');
  98. rateLimitSpan.style.marginRight = '8px';
  99. rateLimitSpan.style.color = '#666';
  100. rateLimitSpan.style.fontSize = '12px';
  101. rateLimitSpan.style.display = 'inline-flex';
  102. rateLimitSpan.style.alignItems = 'center';
  103.  
  104. // Insert to the left of the target link
  105. targetLink.parentNode.insertBefore(rateLimitSpan, targetLink);
  106. log('Rate limit span created');
  107. return true; // Indicate success
  108. } catch (error) {
  109. console.error('[Grok Rate Limit] Error in displayRateLimit:', error);
  110. return false;
  111. }
  112. }
  113.  
  114. // Function to poll for either target element with retries
  115. async function pollForTarget(maxAttempts = 3, interval = 3000) {
  116. try {
  117. let attempts = 0;
  118. while (attempts < maxAttempts) {
  119. // Early exit if span already exists
  120. if (document.querySelector('span.rate-limit-span')) {
  121. log('Rate limit span already exists, stopping polling');
  122. return;
  123. }
  124. const success = await displayRateLimit();
  125. if (success) {
  126. return; // Exit once displayed
  127. }
  128. attempts++;
  129. await new Promise(resolve => setTimeout(resolve, interval));
  130. }
  131. log('Max attempts reached, could not find either target chat link');
  132. } catch (error) {
  133. console.error('[Grok Rate Limit] Error in pollForTarget:', error);
  134. }
  135. }
  136.  
  137. // Run initially with a delay to allow page to load
  138. try {
  139. setTimeout(() => {
  140. pollForTarget();
  141. }, 2000); // Start after 2 seconds
  142. } catch (error) {
  143. console.error('[Grok Rate Limit] Error setting up delayed polling:', error);
  144. }
  145.  
  146. // Periodically refresh rate limit (every 120 seconds)
  147. try {
  148. setInterval(async () => {
  149. const existingSpan = document.querySelector('span.rate-limit-span');
  150. if (existingSpan) {
  151. const rateLimitData = await fetchRateLimit();
  152. if (rateLimitData) {
  153. const remaining = rateLimitData.remainingQueries ?? 'Unknown';
  154. const total = rateLimitData.totalQueries ?? 'Unknown';
  155. existingSpan.textContent = `Queries left: ${remaining} of ${total} `;
  156. log('Rate limit span refreshed');
  157. }
  158. }
  159. }, 120000); // Refresh every 2 minutes
  160. } catch (error) {
  161. console.error('[Grok Rate Limit] Error setting up periodic refresh:', error);
  162. }
  163.  
  164. // Add click listener for the send button SVG to refresh rate limit after 1 second
  165. try {
  166. document.addEventListener('click', async (event) => {
  167. const svg = event.target.closest('svg[width="20"][height="20"][viewBox="0 0 24 24"][fill="none"][class="stroke-[2] relative"]');
  168. if (svg && svg.querySelector('path[d="M5 11L12 4M12 4L19 11M12 4V21"]')) {
  169. log('Send button SVG clicked, scheduling refresh');
  170. setTimeout(async () => {
  171. const existingSpan = document.querySelector('span.rate-limit-span');
  172. if (existingSpan) {
  173. const rateLimitData = await fetchRateLimit();
  174. if (rateLimitData) {
  175. const remaining = rateLimitData.remainingQueries ?? 'Unknown';
  176. const total = rateLimitData.totalQueries ?? 'Unknown';
  177. existingSpan.textContent = `Queries left: ${remaining} of ${total} `;
  178. log('Rate limit span refreshed after click');
  179. }
  180. } else {
  181. // If no span exists, trigger full display logic
  182. await displayRateLimit();
  183. }
  184. }, 1000); // Refresh after 1 second
  185. }
  186. });
  187. } catch (error) {
  188. console.error('[Grok Rate Limit] Error setting up click listener:', error);
  189. }
  190. })();