Grok.com Rate Limit Display

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

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