您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Toggle feature flags on grok.com to test out new features
// ==UserScript== // @name Grok Feature Flags // @namespace http://tampermonkey.net/ // @version 1.0 // @description Toggle feature flags on grok.com to test out new features // @author Blankspeaker // @match https://grok.com/* // @grant none // @license GNU GPLv3 // ==/UserScript== (async function() { 'use strict'; try { // Function to find and fetch the JavaScript file containing local_feature_flags async function findFlagsFile() { const scripts = Array.from(document.getElementsByTagName('script')).map(script => script.src).filter(src => src); for (const src of scripts) { if (src.includes('.js') && !src.includes('polyfill')) { try { const response = await fetch(src); const text = await response.text(); if (text.includes('"local_feature_flags"')) { console.log("Found flags file:", src); return { src, text }; } } catch (error) { console.warn("Failed to fetch script:", src, error); } } } console.error("No JavaScript file found containing local_feature_flags."); return null; } // Function to scrape flags from the JavaScript file function scrapeFlags(fileContent) { // Match flag definitions like SHOW_FAVORITE_BUTTON: e("show_favorite_button") const regex = /\b([A-Z_]+)\s*:\s*e\s*\(\s*"([a-z_]+)"\s*\)/g; const flags = {}; let match; while ((match = regex.exec(fileContent)) !== null) { const upperKey = match[1]; // e.g., SHOW_FAVORITE_BUTTON const lowerKey = match[2]; // e.g., show_favorite_button flags[lowerKey] = false; flags[upperKey] = false; } // Handle non-boolean flags (preserve if present) const nonBooleanRegex = /\b([A-Z_]+)\s*:\s*t\s*\(\s*"([a-z_]+)"\s*\)/g; while ((match = nonBooleanRegex.exec(fileContent)) !== null) { const upperKey = match[1]; const lowerKey = match[2]; // Only include if already in localStorage (to avoid UI issues) if (currentFlags[upperKey] !== undefined) flags[upperKey] = currentFlags[upperKey]; if (currentFlags[lowerKey] !== undefined) flags[lowerKey] = currentFlags[lowerKey]; } return flags; } // Check localStorage availability (for incognito mode) let localStorageAvailable = true; try { localStorage.setItem("test", "test"); localStorage.removeItem("test"); } catch (error) { localStorageAvailable = false; console.warn("localStorage is restricted (e.g., incognito mode):", error); } // Read current flags from localStorage let rawFlags = localStorageAvailable ? localStorage.getItem("local_feature_flags") : null; console.log("Raw local_feature_flags:", rawFlags); let currentFlags = rawFlags ? JSON.parse(rawFlags) : {}; // If localStorage is empty or unavailable, scrape flags from source if (Object.keys(currentFlags).length === 0) { console.log("No flags in localStorage. Attempting to scrape from source..."); const flagsFile = await findFlagsFile(); if (flagsFile) { const scrapedFlags = scrapeFlags(flagsFile.text); if (Object.keys(scrapedFlags).length > 0) { currentFlags = { ...scrapedFlags }; if (localStorageAvailable) { localStorage.setItem("local_feature_flags", JSON.stringify(currentFlags)); console.log("Scraped and initialized flags:", Object.keys(currentFlags)); } else { console.warn("Using scraped flags in memory due to localStorage restrictions."); } } else { console.error("No flags scraped from source."); } } else { console.error("Unable to initialize flags: No source file found."); } } // Group flags by uppercase name to avoid duplicate display let flagGroups = {}; Object.keys(currentFlags).forEach(flag => { const normalized = flag.toUpperCase(); if (!flagGroups[normalized]) { flagGroups[normalized] = []; } flagGroups[normalized].push(flag); }); let displayFlags = Object.keys(flagGroups); // Log flag counts console.log("Total flags:", Object.keys(currentFlags).length, "Unique flags (after normalization):", displayFlags.length); // Create UI element for flag picker let ui = document.createElement("div"); ui.id = "feature-flags-ui"; ui.style.display = "none"; // Initially hidden ui.innerHTML = ` <div class="title-bar"> <span>Feature Flags</span> <button id="minimize-btn">_</button> <button id="close-btn">X</button> </div> <div class="content"> <div class="flag-list"> ${displayFlags.length > 0 ? displayFlags.map(normalizedFlag => { // Use the first original key for checkbox state const originalKeys = flagGroups[normalizedFlag]; const primaryKey = originalKeys[0]; return ` <label> <input type="checkbox" data-flag="${normalizedFlag}" ${typeof currentFlags[primaryKey] === 'boolean' && currentFlags[primaryKey] ? 'checked' : ''}> ${normalizedFlag} </label> `; }).join("") : '<p>No feature flags available. Try opening the settings menu to populate flags.</p>'} </div> <button id="save-btn">Save</button> </div> `; // Append UI to body document.body.appendChild(ui); // Load saved UI state let uiState = localStorageAvailable ? JSON.parse(localStorage.getItem("feature_flags_ui_state") || "{}") : {}; if (uiState.left && uiState.top) { ui.style.left = uiState.left + "px"; ui.style.top = uiState.top + "px"; } if (uiState.minimized) { ui.classList.add("minimized"); } if (uiState.visible) { ui.style.display = "block"; } // Add CSS for styling, dragging, and fixed save button let style = document.createElement("style"); style.textContent = ` #feature-flags-ui { position: absolute; top: 10px; left: 10px; width: 400px; /* Wider UI to prevent text wrapping */ background: #333333; /* Dark gray background */ color: #ffffff; /* White text */ border: 1px solid #555555; box-shadow: 2px 2px 5px rgba(0,0,0,0.5); font-family: Arial, sans-serif; z-index: 10000; /* Ensure UI is above other elements */ } #feature-flags-ui .title-bar { background: #555555; /* Slightly lighter gray for title bar */ color: #ffffff; padding: 5px; cursor: move; display: flex; justify-content: space-between; align-items: center; } #feature-flags-ui .title-bar span { flex-grow: 1; } #feature-flags-ui .content { padding: 10px; display: flex; flex-direction: column; max-height: 400px; } #feature-flags-ui .flag-list { flex: 1; overflow-y: auto; padding-bottom: 10px; } #feature-flags-ui.minimized .content { display: none; } #feature-flags-ui label { display: flex; /* Keep checkbox and text inline */ align-items: center; margin-bottom: 5px; color: #ffffff; /* White text for labels */ } #feature-flags-ui label input { margin-right: 8px; /* Space between checkbox and text */ } #feature-flags-ui button { color: #ffffff; background: #555555; border: 1px solid #777777; } #feature-flags-ui #save-btn { position: sticky; bottom: 0; margin-top: 10px; padding: 5px; width: 100%; box-sizing: border-box; } #feature-flags-ui p { color: #ffffff; margin: 0; } `; document.head.appendChild(style); // Add menu item for Feature Flags const manageSubscriptionItem = document.querySelector('div[role="menuitem"] svg path[d="M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6"]'); if (manageSubscriptionItem) { const menuItem = document.createElement("div"); menuItem.setAttribute("role", "menuitem"); menuItem.className = "relative flex select-none items-center cursor-pointer px-3 py-2 rounded-xl text-sm outline-none focus:bg-button-ghost-hover"; menuItem.setAttribute("tabindex", "-1"); menuItem.setAttribute("data-orientation", "vertical"); menuItem.setAttribute("data-radix-collection-item", ""); menuItem.innerHTML = ` <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="stroke-[2] text-neutral-400 mr-2" stroke-width="2"> <path d="M10 4V4C8.13623 4 7.20435 4 6.46927 4.30448C5.48915 4.71046 4.71046 5.48915 4.30448 6.46927C4 7.20435 4 8.13623 4 10V13.6C4 15.8402 4 16.9603 4.43597 17.816C4.81947 18.5686 5.43139 19.1805 6.18404 19.564C7.03968 20 8.15979 20 10.4 20H14C15.8638 20 16.7956 20 17.5307 19.6955C18.5108 19.2895 19.2895 18.5108 19.6955 17.5307C20 16.7956 20 15.8638 20 14V14" stroke="currentColor" stroke-linecap="square"></path> <path d="M12.4393 14.5607L19.5 7.5C20.3284 6.67157 20.3284 5.32843 19.5 4.5C18.6716 3.67157 17.3284 3.67157 16.5 4.5L9.43934 11.5607C9.15804 11.842 9 12.2235 9 12.6213V15H11.3787C11.7765 15 12.158 14.842 12.4393 14.5607Z" stroke="currentColor" stroke-linecap="square"></path> </svg> Feature Flags `; manageSubscriptionItem.parentElement.insertAdjacentElement("afterend", menuItem); // Toggle flag picker visibility on menu item click menuItem.addEventListener("click", () => { ui.style.display = ui.style.display === "none" ? "block" : "none"; uiState.visible = ui.style.display === "block"; if (localStorageAvailable) { localStorage.setItem("feature_flags_ui_state", JSON.stringify(uiState)); } }); } else { console.warn("Manage Subscription menu item not found. Flag picker will be shown by default."); ui.style.display = "block"; // Fallback to visible if menu item not found } // Make UI draggable and save position let titleBar = ui.querySelector(".title-bar"); let isDragging = false; let offsetX, offsetY; titleBar.addEventListener("mousedown", (e) => { isDragging = true; offsetX = e.clientX - ui.offsetLeft; offsetY = e.clientY - ui.offsetTop; }); document.addEventListener("mousemove", (e) => { if (isDragging) { ui.style.left = (e.clientX - offsetX) + "px"; ui.style.top = (e.clientY - offsetY) + "px"; // Update saved position uiState.left = e.clientX - offsetX; uiState.top = e.clientY - offsetY; if (localStorageAvailable) { localStorage.setItem("feature_flags_ui_state", JSON.stringify(uiState)); } } }); document.addEventListener("mouseup", () => { isDragging = false; }); // Minimize button and save state ui.querySelector("#minimize-btn").addEventListener("click", () => { ui.classList.toggle("minimized"); uiState.minimized = ui.classList.contains("minimized"); if (localStorageAvailable) { localStorage.setItem("feature_flags_ui_state", JSON.stringify(uiState)); } }); // Close button ui.querySelector("#close-btn").addEventListener("click", () => { ui.style.display = "none"; uiState.visible = false; if (localStorageAvailable) { localStorage.setItem("feature_flags_ui_state", JSON.stringify(uiState)); } }); // Save button with auto-refresh ui.querySelector("#save-btn").addEventListener("click", () => { let modifiedFlags = { ...currentFlags }; displayFlags.forEach(normalizedFlag => { let checkbox = ui.querySelector(`input[data-flag="${normalizedFlag}"]`); if (checkbox) { // Update all original keys for this normalized flag flagGroups[normalizedFlag].forEach(originalKey => { modifiedFlags[originalKey] = checkbox.checked; }); } }); if (localStorageAvailable) { localStorage.setItem("local_feature_flags", JSON.stringify(modifiedFlags)); } console.log("Flags saved. Reloading page..."); currentFlags = modifiedFlags; // Update in-memory flags for incognito location.reload(); // Auto-refresh the page }); console.log("Grok Feature Flags Toggler loaded successfully with", displayFlags.length, "unique flags."); } catch (error) { console.error("Error in Grok Feature Flags Toggler:", error); } })();