Grok.com Rate Limit Display

Shows you how many queries you have left/total in your rolling 2 hour window on grok.com

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

  1. // ==UserScript==
  2. // @name Grok.com Rate Limit Display
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1
  5. // @description Shows you how many queries you have left/total in your rolling 2 hour window on grok.com
  6. // @author Blankspeaker (based on the chrome extension by CursedAtom) & Grok
  7. // @match https://grok.com/*
  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 poll for an element
  29. async function pollForElement(selector, maxAttempts = 5, interval = 1000) {
  30. let attempts = 0;
  31. while (attempts < maxAttempts) {
  32. const element = document.querySelector(selector);
  33. if (element) {
  34. log(`Element found: ${selector}`);
  35. return element;
  36. }
  37. attempts++;
  38. log(`Element not found: ${selector}, attempt ${attempts}/${maxAttempts}`);
  39. await new Promise(resolve => setTimeout(resolve, interval));
  40. }
  41. log(`Max attempts reached, could not find element: ${selector}`);
  42. return null;
  43. }
  44.  
  45. // Function to fetch rate limit
  46. async function fetchRateLimit() {
  47. try {
  48. // Use a simpler selector to match the span containing "Grok 2" or "Grok 3"
  49. const selector = 'span.inline-block.text-primary.text-xs';
  50. const modelSpan = await pollForElement(selector);
  51. let modelName = 'grok-3'; // Default to grok-3
  52. if (modelSpan) {
  53. const modelText = modelSpan.textContent.trim();
  54. if (modelText === 'Grok 2') {
  55. modelName = 'grok-2';
  56. } else if (modelText === 'Grok 3') {
  57. modelName = 'grok-3';
  58. }
  59. log(`Model detected: ${modelText}, setting modelName to ${modelName}`);
  60. } else {
  61. log('Model span not found, defaulting to modelName: grok-3');
  62. }
  63.  
  64. const response = await fetch('https://grok.com/rest/rate-limits', {
  65. method: 'POST',
  66. headers: {
  67. 'Content-Type': 'application/json',
  68. },
  69. body: JSON.stringify({
  70. requestKind: 'DEFAULT',
  71. modelName: modelName,
  72. }),
  73. credentials: 'include',
  74. });
  75.  
  76. if (!response.ok) {
  77. throw new Error(`HTTP error: ${response.status}`);
  78. }
  79.  
  80. const data = await response.json();
  81. log('Rate limit data: ' + JSON.stringify(data));
  82. return data;
  83. } catch (error) {
  84. console.error('[Grok Rate Limit] Failed to fetch rate limit:', error);
  85. return null;
  86. }
  87. }
  88.  
  89. // Function to update or create the rate limit display
  90. async function displayRateLimit() {
  91. try {
  92. // Check if rate limit span already exists
  93. const existingSpan = document.querySelector('span.rate-limit-span');
  94. if (existingSpan) {
  95. // Update existing span
  96. const rateLimitData = await fetchRateLimit();
  97. if (rateLimitData) {
  98. const remaining = rateLimitData.remainingQueries ?? 'Unknown';
  99. const total = rateLimitData.totalQueries ?? 'Unknown';
  100. existingSpan.textContent = `Queries left: ${remaining} of ${total} `;
  101. }
  102. log('Rate limit span updated');
  103. return true; // Indicate span exists
  104. }
  105.  
  106. // Try primary target: <a> with href="/chat" (for private chat page)
  107. let targetLink = document.querySelector('a.inline-flex.items-center.justify-center.gap-2[href="/chat"]');
  108. if (!targetLink) {
  109. // Fall back to alternative target: <a> with href="/chat#private" (for main page)
  110. targetLink = document.querySelector('a.inline-flex.items-center.justify-center.gap-2[href="/chat#private"][aria-label="Switch to Private Chat"]');
  111. if (!targetLink) {
  112. // Broaden the selector to any link containing "chat" in href
  113. targetLink = document.querySelector('a[href*="/chat"]');
  114. if (!targetLink) {
  115. log('All target chat links not found');
  116. return false; // Indicate failure to find target
  117. }
  118. }
  119. }
  120.  
  121. // Fetch rate limit data
  122. const rateLimitData = await fetchRateLimit();
  123. if (!rateLimitData) {
  124. return false;
  125. }
  126.  
  127. // Extract rate limit info
  128. const remaining = rateLimitData.remainingQueries ?? 'Unknown';
  129. const total = rateLimitData.totalQueries ?? 'Unknown';
  130.  
  131. // Create rate limit display element
  132. const rateLimitSpan = document.createElement('span');
  133. rateLimitSpan.textContent = `Queries left: ${remaining} of ${total} `;
  134. rateLimitSpan.classList.add('rate-limit-span');
  135. rateLimitSpan.style.marginRight = '8px';
  136. rateLimitSpan.style.color = '#666';
  137. rateLimitSpan.style.fontSize = '12px';
  138. rateLimitSpan.style.display = 'inline-flex';
  139. rateLimitSpan.style.alignItems = 'center';
  140.  
  141. // Insert to the left of the target link
  142. targetLink.parentNode.insertBefore(rateLimitSpan, targetLink);
  143. log('Rate limit span created');
  144. return true; // Indicate success
  145. } catch (error) {
  146. console.error('[Grok Rate Limit] Error in displayRateLimit:', error);
  147. return false;
  148. }
  149. }
  150.  
  151. // Function to poll for either target element with retries
  152. async function pollForTarget(maxAttempts = 5, interval = 3000) {
  153. try {
  154. let attempts = 0;
  155. while (attempts < maxAttempts) {
  156. // Early exit if span already exists
  157. if (document.querySelector('span.rate-limit-span')) {
  158. log('Rate limit span already exists, stopping polling');
  159. return;
  160. }
  161. const success = await displayRateLimit();
  162. if (success) {
  163. return; // Exit once displayed
  164. }
  165. attempts++;
  166. log(`Polling attempt ${attempts}/${maxAttempts}`);
  167. await new Promise(resolve => setTimeout(resolve, interval));
  168. }
  169. log('Max attempts reached, could not find either target chat link');
  170. } catch (error) {
  171. console.error('[Grok Rate Limit] Error in pollForTarget:', error);
  172. }
  173. }
  174.  
  175. // Run initially with a delay to allow page to load
  176. try {
  177. setTimeout(() => {
  178. pollForTarget();
  179. }, 2000); // Start after 2 seconds
  180. } catch (error) {
  181. console.error('[Grok Rate Limit] Error setting up delayed polling:', error);
  182. }
  183.  
  184. // Periodically refresh rate limit (every 120 seconds)
  185. try {
  186. setInterval(async () => {
  187. const existingSpan = document.querySelector('span.rate-limit-span');
  188. if (existingSpan) {
  189. const rateLimitData = await fetchRateLimit();
  190. if (rateLimitData) {
  191. const remaining = rateLimitData.remainingQueries ?? 'Unknown';
  192. const total = rateLimitData.totalQueries ?? 'Unknown';
  193. existingSpan.textContent = `Queries left: ${remaining} of ${total} `;
  194. log('Rate limit span refreshed');
  195. }
  196. }
  197. }, 30000); // Refresh every 30 seconds
  198. } catch (error) {
  199. console.error('[Grok Rate Limit] Error setting up periodic refresh:', error);
  200. }
  201.  
  202. // Add click listener for the send button SVG to refresh rate limit after 1 second
  203. try {
  204. document.addEventListener('click', async (event) => {
  205. const svg = event.target.closest('svg[width="20"][height="20"][viewBox="0 0 24 24"][fill="none"][class="stroke-[2] relative"]');
  206. if (svg && svg.querySelector('path[d="M5 11L12 4M12 4L19 11M12 4V21"]')) {
  207. log('Send button SVG clicked, scheduling refresh');
  208. setTimeout(async () => {
  209. const existingSpan = document.querySelector('span.rate-limit-span');
  210. if (existingSpan) {
  211. const rateLimitData = await fetchRateLimit();
  212. if (rateLimitData) {
  213. const remaining = rateLimitData.remainingQueries ?? 'Unknown';
  214. const total = rateLimitData.totalQueries ?? 'Unknown';
  215. existingSpan.textContent = `Queries left: ${remaining} of ${total} `;
  216. log('Rate limit span refreshed after click');
  217. }
  218. } else {
  219. // If no span exists, trigger full display logic
  220. await displayRateLimit();
  221. }
  222. }, 1000); // Refresh after 1 second
  223. }
  224. });
  225. } catch (error) {
  226. console.error('[Grok Rate Limit] Error setting up click listener:', error);
  227. }
  228. })();