Grok ratelimit indicator

Shows ratelimit information on Grok

  1. // ==UserScript==
  2. // @name Grok ratelimit indicator
  3. // @namespace https://6942020.xyz/
  4. // @version 1.2
  5. // @description Shows ratelimit information on Grok
  6. // @author WadeGrimridge
  7. // @match https://grok.com/*
  8. // @license MIT
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. "use strict";
  14.  
  15. let rateInfoElement = null;
  16. const modelRateLimits = {
  17. "grok-latest": null,
  18. "grok-3": {
  19. DEFAULT: null,
  20. REASONING: null,
  21. DEEPSEARCH: null,
  22. },
  23. };
  24. const modelDisplayNames = {
  25. "grok-latest": "Grok 2",
  26. "grok-3": {
  27. DEFAULT: "Grok 3",
  28. REASONING: "Think",
  29. DEEPSEARCH: "DeepSearch",
  30. DEEPERSEARCH: "DeeperSearch",
  31. },
  32. };
  33.  
  34. function formatTime(seconds) {
  35. if (seconds >= 3600) {
  36. const hours = Math.floor(seconds / 3600);
  37. const remainingMinutes = Math.floor((seconds % 3600) / 60);
  38. return remainingMinutes > 0
  39. ? `${hours}h ${remainingMinutes}m`
  40. : `${hours}h`;
  41. }
  42. const minutes = Math.floor(seconds / 60);
  43. const remainingSeconds = seconds % 60;
  44. return remainingSeconds > 0
  45. ? `${minutes}m ${remainingSeconds}s`
  46. : `${minutes}m`;
  47. }
  48.  
  49. async function fetchRateLimit(
  50. modelName,
  51. requestKind = "DEFAULT",
  52. attempt = 1
  53. ) {
  54. try {
  55. const response = await fetch("/rest/rate-limits", {
  56. method: "POST",
  57. headers: { "Content-Type": "application/json" },
  58. body: JSON.stringify({ requestKind, modelName }),
  59. });
  60.  
  61. if (response.status !== 200 && attempt <= 10) {
  62. await new Promise((resolve) => setTimeout(resolve, 1000));
  63. return fetchRateLimit(modelName, requestKind, attempt + 1);
  64. }
  65.  
  66. const data = await response.json();
  67. if (!isValidRateData(data)) return;
  68.  
  69. updateRateInfo(data, modelName, requestKind);
  70. } catch (error) {
  71. console.error(`[grok-ratelimit] Rate limit fetch failed:`, error);
  72. if (attempt <= 10) return;
  73. if (rateInfoElement) {
  74. rateInfoElement.textContent = "Couldn't fetch ratelimit info";
  75. }
  76. }
  77. }
  78.  
  79. function isValidRateData(data) {
  80. return (
  81. data &&
  82. typeof data.remainingQueries === "number" &&
  83. typeof data.totalQueries === "number"
  84. );
  85. }
  86.  
  87. function updateRateInfo(data, modelName, requestKind = "DEFAULT") {
  88. if (!rateInfoElement) return;
  89.  
  90. if (modelName === "grok-3") {
  91. modelRateLimits[modelName][requestKind] = data;
  92. } else {
  93. modelRateLimits[modelName] = data;
  94. }
  95.  
  96. const lines = [];
  97.  
  98. for (const kind of ["DEFAULT", "REASONING", "DEEPSEARCH", "DEEPERSEARCH"]) {
  99. const modelData = modelRateLimits["grok-3"][kind];
  100. if (!modelData) continue;
  101.  
  102. const timeStr = formatTime(modelData.windowSizeSeconds);
  103. const displayName = modelDisplayNames["grok-3"][kind];
  104. lines.push(
  105. `${displayName}: ${modelData.remainingQueries}/${modelData.totalQueries} (${timeStr})`
  106. );
  107. }
  108.  
  109. const grok2Data = modelRateLimits["grok-latest"];
  110. if (grok2Data) {
  111. const timeStr = formatTime(grok2Data.windowSizeSeconds);
  112. lines.push(
  113. `${modelDisplayNames["grok-latest"]}: ${grok2Data.remainingQueries}/${grok2Data.totalQueries} (${timeStr})`
  114. );
  115. }
  116.  
  117. rateInfoElement.textContent = lines.join(" | ");
  118. }
  119.  
  120. const originalFetch = window.fetch;
  121. window.fetch = async function (...args) {
  122. const [resource, options] = args;
  123. const url =
  124. resource instanceof Request ? resource.url : resource.toString();
  125.  
  126. if (!url.includes("/rest/rate-limits")) {
  127. return originalFetch.apply(this, args);
  128. }
  129.  
  130. const requestBody = JSON.parse(options.body);
  131. const modelName = requestBody.modelName;
  132. const requestKind = requestBody.requestKind;
  133. const response = await originalFetch.apply(this, args);
  134.  
  135. const clone = response.clone();
  136. clone.json().then((data) => {
  137. if (!isValidRateData(data)) return;
  138. updateRateInfo(data, modelName, requestKind);
  139. });
  140.  
  141. return response;
  142. };
  143.  
  144. function createRateInfoElement() {
  145. const targetDiv = document.querySelector(
  146. 'main div:has(> a[aria-label="Home page"])'
  147. );
  148. if (targetDiv && !rateInfoElement) {
  149. const headerDiv = targetDiv.parentElement;
  150.  
  151. headerDiv.classList.remove(
  152. "@[80rem]/nav:h-0",
  153. "@[80rem]/nav:top-8",
  154. "@[80rem]/nav:from-transparent",
  155. "@[80rem]/nav:via-transparent"
  156. );
  157.  
  158. rateInfoElement = document.createElement("div");
  159. rateInfoElement.className = "ml-2 text-sm break-words";
  160. rateInfoElement.style.maxWidth = "calc(100vw - 240px)";
  161. rateInfoElement.textContent = "Fetching ratelimit info...";
  162. targetDiv.appendChild(rateInfoElement);
  163.  
  164. const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
  165.  
  166. (async () => {
  167. await fetchRateLimit("grok-3", "DEFAULT");
  168. await sleep(100);
  169. await fetchRateLimit("grok-3", "REASONING");
  170. await sleep(100);
  171. await fetchRateLimit("grok-3", "DEEPSEARCH");
  172. await sleep(100);
  173. await fetchRateLimit("grok-3", "DEEPERSEARCH");
  174. await sleep(100);
  175. await fetchRateLimit("grok-latest");
  176. })();
  177. }
  178. }
  179.  
  180. function waitForElement() {
  181. const targetDiv = document.querySelector(
  182. 'main div:has(> a[aria-label="Home page"])'
  183. );
  184. if (targetDiv) {
  185. createRateInfoElement();
  186. } else {
  187. requestAnimationFrame(waitForElement);
  188. }
  189. }
  190.  
  191. waitForElement();
  192. })();