您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays rate limit (Queries left: Remaining of Total / WindowSize) on Grok.com
您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
- // ==UserScript==
- // @name Grok.com Rate Limit Display
- // @namespace http://tampermonkey.net/
- // @version 1.2
- // @description Displays rate limit (Queries left: Remaining of Total / WindowSize) on Grok.com
- // @author Blankspeaker (based on the chrome extension by CursedAtom) & Grok
- // @match https://grok.com/*
- // @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 poll for an element
- async function pollForElement(selector, maxAttempts = 5, interval = 1000) {
- let attempts = 0;
- while (attempts < maxAttempts) {
- const element = document.querySelector(selector);
- if (element) {
- log(`Element found: ${selector}`);
- return element;
- }
- attempts++;
- log(`Element not found: ${selector}, attempt ${attempts}/${maxAttempts}`);
- await new Promise(resolve => setTimeout(resolve, interval));
- }
- log(`Max attempts reached, could not find element: ${selector}`);
- return null;
- }
- // Function to convert seconds to hours for display
- function secondsToHours(seconds) {
- if (!seconds || isNaN(seconds)) {
- return 'Unknown';
- }
- const hours = Math.floor(seconds / 3600);
- return hours === 0 ? '<1' : hours;
- }
- // Function to fetch rate limit
- async function fetchRateLimit() {
- try {
- // Use a simpler selector to match the span containing "Grok 2" or "Grok 3"
- const selector = 'span.inline-block.text-primary.text-xs';
- const modelSpan = await pollForElement(selector);
- let modelName = 'grok-3'; // Default to grok-3
- if (modelSpan) {
- const modelText = modelSpan.textContent.trim();
- if (modelText === 'Grok 2') {
- modelName = 'grok-2';
- } else if (modelText === 'Grok 3') {
- modelName = 'grok-3';
- }
- log(`Model detected: ${modelText}, setting modelName to ${modelName}`);
- } else {
- log('Model span not found, defaulting to modelName: grok-3');
- }
- const response = await fetch('https://grok.com/rest/rate-limits', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- requestKind: 'DEFAULT',
- modelName: modelName,
- }),
- 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';
- const windowSizeHours = secondsToHours(rateLimitData.windowSizeSeconds);
- existingSpan.textContent = `Queries left: ${remaining} of ${total} / ${windowSizeHours}hr `;
- }
- log('Rate limit span updated');
- return true; // Indicate span exists
- }
- // Try primary target: <a> with href="/chat" (for private chat page)
- 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" (for main page)
- targetLink = document.querySelector('a.inline-flex.items-center.justify-center.gap-2[href="/chat#private"][aria-label="Switch to Private Chat"]');
- if (!targetLink) {
- // Broaden the selector to any link containing "chat" in href
- targetLink = document.querySelector('a[href*="/chat"]');
- if (!targetLink) {
- log('All 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';
- const windowSizeHours = secondsToHours(rateLimitData.windowSizeSeconds);
- // Create rate limit display element
- const rateLimitSpan = document.createElement('span');
- rateLimitSpan.textContent = `Queries left: ${remaining} of ${total} / ${windowSizeHours}hr `;
- 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 = 5, 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++;
- log(`Polling attempt ${attempts}/${maxAttempts}`);
- 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';
- const windowSizeHours = secondsToHours(rateLimitData.windowSizeSeconds);
- existingSpan.textContent = `Queries left: ${remaining} of ${total} / ${windowSizeHours}hr `;
- 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';
- const windowSizeHours = secondsToHours(rateLimitData.windowSizeSeconds);
- existingSpan.textContent = `Queries left: ${remaining} of ${total} / ${windowSizeHours}hr `;
- 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);
- }
- })();