Guest User

Grok Console

a guest
Mar 21st, 2026
1,571
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.48 KB | None | 0 0
  1. // ==UserScript==
  2. // @name xAI Console Tools (Reset + 2K Toggle + JPEG Save)
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.1
  5. // @description Reset button, optional 2K forcing, and JPEG download for xAI Console
  6. // @match https://console.x.ai/playground/imagine*
  7. // @match https://console.x.ai/team/*/imagine*
  8. // @grant none
  9. // @run-at document-start
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. const TAG = '[xAI Console Tools]';
  16. const PANEL_ID = 'xai-tools-panel';
  17. const RESET_BTN_ID = 'xai-reset-btn';
  18. const TOGGLE_BTN_ID = 'xai-2k-toggle-btn';
  19. const GAP_PX = 8;
  20. const JPEG_QUALITY = 0.95;
  21. const FORCE_2K_KEY = 'gc_force2k_v1';
  22. const BYPASS_ATTR = 'data-xai-jpeg-bypass';
  23.  
  24. function log(msg, type = 'log') {
  25. const fn =
  26. type === 'error' ? console.error :
  27. type === 'warn' ? console.warn :
  28. console.log;
  29. fn(`${TAG} ${msg}`);
  30. }
  31.  
  32. function isVisible(el) {
  33. if (!(el instanceof Element)) return false;
  34. const style = getComputedStyle(el);
  35. if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
  36. return false;
  37. }
  38. const rect = el.getBoundingClientRect();
  39. return rect.width > 0 && rect.height > 0;
  40. }
  41.  
  42. function getText(el) {
  43. if (!(el instanceof Element)) return '';
  44. return [
  45. el.textContent || '',
  46. el.getAttribute('aria-label') || '',
  47. el.getAttribute('title') || ''
  48. ].join(' ').replace(/\s+/g, ' ').trim().toLowerCase();
  49. }
  50.  
  51. function isForce2kEnabled() {
  52. try {
  53. return localStorage.getItem(FORCE_2K_KEY) === '1';
  54. } catch {
  55. return false;
  56. }
  57. }
  58.  
  59. function setForce2kEnabled(enabled) {
  60. try {
  61. localStorage.setItem(FORCE_2K_KEY, enabled ? '1' : '0');
  62. } catch {}
  63. }
  64.  
  65. function modifyPayload(originalBody) {
  66. try {
  67. const body = JSON.parse(originalBody);
  68. body.resolution = '2k';
  69. log('resolution -> 2k', 'warn');
  70. return JSON.stringify(body);
  71. } catch (e) {
  72. log(`modify payload error: ${e.message}`, 'error');
  73. return originalBody;
  74. }
  75. }
  76.  
  77. function isImageApiUrl(url) {
  78. try {
  79. const u = new URL(url, location.href);
  80. return /^\/v1\/images(?:\/|$)/.test(u.pathname);
  81. } catch {
  82. return typeof url === 'string' && url.includes('/v1/images');
  83. }
  84. }
  85.  
  86. const nativeFetch = window.fetch.bind(window);
  87.  
  88. window.fetch = async function (input, init) {
  89. let nextInput = input;
  90. let nextInit = init;
  91.  
  92. try {
  93. if (!isForce2kEnabled()) {
  94. return nativeFetch(input, init);
  95. }
  96.  
  97. const isReq = input instanceof Request;
  98. const url = isReq ? input.url : String(input || '');
  99. const method = ((init && init.method) || (isReq ? input.method : 'GET') || 'GET').toUpperCase();
  100.  
  101. if (method === 'POST' && isImageApiUrl(url)) {
  102. if (typeof init?.body === 'string') {
  103. nextInit = { ...init, body: modifyPayload(init.body) };
  104. } else if (isReq && !init) {
  105. const cloned = input.clone();
  106. const textBody = await cloned.text();
  107. const patchedBody = modifyPayload(textBody);
  108. if (patchedBody !== textBody) {
  109. nextInput = new Request(input, { body: patchedBody });
  110. }
  111. }
  112. }
  113. } catch (e) {
  114. log(`fetch interception error: ${e.message}`, 'error');
  115. }
  116.  
  117. return nativeFetch(nextInput, nextInit);
  118. };
  119.  
  120. function resetLimit() {
  121. log('Запуск сброса (images + video + credits)', 'warn');
  122.  
  123. const lsPatterns = [
  124. /flushAfter/i,
  125. /imagine/i,
  126. /generation/i,
  127. /grok-imagine/i,
  128. /mixpanel|mp_/i,
  129. /distinct_id|device_id/i,
  130. /_rst|_r/i,
  131. /event|payload/i,
  132. /quota|limit|usage|count|reset/i,
  133. /video|vid|vidgen|grok-imagine-video/i,
  134. /request_type/i,
  135. /credit|credits|demand|highdemand|purchase|balance|high demand/i
  136. ];
  137.  
  138. for (let i = localStorage.length - 1; i >= 0; i--) {
  139. const key = localStorage.key(i);
  140. if (key && lsPatterns.some(p => p.test(key))) {
  141. localStorage.removeItem(key);
  142. console.log('[LS удалён]:', key);
  143. }
  144. }
  145.  
  146. for (let i = sessionStorage.length - 1; i >= 0; i--) {
  147. const key = sessionStorage.key(i);
  148. if (key && lsPatterns.some(p => p.test(key))) {
  149. sessionStorage.removeItem(key);
  150. console.log('[Session удалён]:', key);
  151. }
  152. }
  153.  
  154. const cookiePatterns = [
  155. 'mp_', 'mixpanel', 'distinct_id', 'device_id',
  156. 'xai_', 'imagine', 'console', 'grok', '_rst', '_r',
  157. 'video', 'vid', 'credit', 'demand', 'high', 'purchase', 'balance'
  158. ];
  159.  
  160. document.cookie.split(';').forEach(c => {
  161. const name = c.split('=')[0]?.trim();
  162. if (!name) return;
  163. if (cookiePatterns.some(p => name.toLowerCase().includes(p.toLowerCase()))) {
  164. ['.x.ai', 'console.x.ai', '.console.x.ai', ''].forEach(d => {
  165. ['/', '/playground', '/playground/imagine', '/team', ''].forEach(p => {
  166. document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=${p}${d ? ';domain=' + d : ''}`;
  167. });
  168. });
  169. console.log('[Cookie удалена]:', name);
  170. }
  171. });
  172.  
  173. const u = new URL(location.href);
  174. const now = Date.now();
  175. u.searchParams.set('_r', now);
  176. u.searchParams.set('_rst', now);
  177. u.searchParams.set('_rstv', now);
  178. u.searchParams.set('_vidrst', now);
  179. location.replace(u.toString());
  180. }
  181.  
  182. function makeBaseButton() {
  183. const btn = document.createElement('button');
  184. btn.type = 'button';
  185. btn.className =
  186. 'inline-flex items-center justify-center gap-x-2 rounded-full ' +
  187. 'text-sm font-medium transition-colors focus-visible:outline-none ' +
  188. 'focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none ' +
  189. 'disabled:opacity-50 border border-input text-primary ' +
  190. 'hover:border-primary/15 shadow-sm hover:bg-overlay-hover ' +
  191. 'h-9 px-4 py-2 bg-surface-l1 pointer-events-auto';
  192. btn.style.margin = '0';
  193. btn.style.pointerEvents = 'auto';
  194. btn.style.transition = 'opacity 0.15s ease, transform 0.12s ease, background-color 0.15s ease, box-shadow 0.15s ease';
  195. return btn;
  196. }
  197.  
  198. function update2kButton() {
  199. const btn = document.getElementById(TOGGLE_BTN_ID);
  200. if (!btn) return;
  201.  
  202. const enabled = isForce2kEnabled();
  203. btn.innerHTML = `<span class="min-w-[78px]">${enabled ? '2K: ON' : '2K: OFF'}</span>`;
  204.  
  205. if (enabled) {
  206. btn.style.background = 'rgba(255, 204, 0, 0.08)';
  207. btn.style.boxShadow = 'inset 0 0 0 1px rgba(255, 204, 0, 0.35)';
  208. } else {
  209. btn.style.background = '';
  210. btn.style.boxShadow = '';
  211. }
  212. }
  213.  
  214. function createPanel() {
  215. if (!document.body || document.getElementById(PANEL_ID)) return;
  216.  
  217. const panel = document.createElement('div');
  218. panel.id = PANEL_ID;
  219. panel.style.position = 'fixed';
  220. panel.style.display = 'flex';
  221. panel.style.gap = `${GAP_PX}px`;
  222. panel.style.alignItems = 'center';
  223. panel.style.zIndex = '9999';
  224. panel.style.opacity = '0';
  225. panel.style.pointerEvents = 'auto';
  226. panel.style.transition = 'opacity 0.15s ease';
  227. panel.style.left = '16px';
  228. panel.style.top = '16px';
  229.  
  230. const toggleBtn = makeBaseButton();
  231. toggleBtn.id = TOGGLE_BTN_ID;
  232. toggleBtn.title = 'Включить или выключить форсирование 2K';
  233. toggleBtn.style.minWidth = '110px';
  234. toggleBtn.addEventListener('click', () => {
  235. const next = !isForce2kEnabled();
  236. setForce2kEnabled(next);
  237. update2kButton();
  238. positionPanel();
  239. log(`2K forcing ${next ? 'enabled' : 'disabled'}`, 'warn');
  240. });
  241.  
  242. const resetBtn = makeBaseButton();
  243. resetBtn.id = RESET_BTN_ID;
  244. resetBtn.innerHTML = `
  245. <svg class="size-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  246. <path d="M3 12a9 9 0 1 0 9-9 9 9 0 0 0-9 9Z"></path>
  247. <path d="m15 3-3 3 3 3"></path>
  248. </svg>
  249. <span class="min-w-[90px]">Сбросить лимит</span>
  250. `;
  251. resetBtn.addEventListener('click', resetLimit);
  252.  
  253. panel.appendChild(toggleBtn);
  254. panel.appendChild(resetBtn);
  255. document.body.appendChild(panel);
  256.  
  257. update2kButton();
  258. }
  259.  
  260. function findToolbarAnchor() {
  261. return Array.from(document.querySelectorAll('button[aria-haspopup="menu"]')).find(el => {
  262. if (!isVisible(el)) return false;
  263. const span = el.querySelector('span');
  264. const chevron = el.querySelector('svg.lucide-chevrons-up-down');
  265. return span?.textContent?.trim() === 'Image' && chevron;
  266. });
  267. }
  268.  
  269. function isViewerOpen() {
  270. const controls = Array.from(document.querySelectorAll('button, a, [role="button"]')).filter(el => {
  271. if (!isVisible(el)) return false;
  272. if (el.closest(`#${PANEL_ID}`)) return false;
  273. return true;
  274. });
  275.  
  276. const hasDownload = controls.some(el => /\bdownload\b/.test(getText(el)));
  277. const hasEdit = controls.some(el => /\bedit\b/.test(getText(el)));
  278. return hasDownload && hasEdit;
  279. }
  280.  
  281. function positionPanel() {
  282. const panel = document.getElementById(PANEL_ID);
  283. if (!panel) return;
  284.  
  285. if (isViewerOpen()) {
  286. panel.style.opacity = '0';
  287. panel.style.pointerEvents = 'none';
  288. return;
  289. }
  290.  
  291. const target = findToolbarAnchor();
  292. if (!target) {
  293. panel.style.opacity = '0';
  294. panel.style.pointerEvents = 'none';
  295. return;
  296. }
  297.  
  298. const rect = target.getBoundingClientRect();
  299. const panelRect = panel.getBoundingClientRect();
  300.  
  301. let left = rect.left - panelRect.width - GAP_PX;
  302. let top = rect.top + ((rect.height - panelRect.height) / 2);
  303.  
  304. if (left < 16) left = 16;
  305. if (top < 12) top = 12;
  306.  
  307. panel.style.left = `${left}px`;
  308. panel.style.top = `${top}px`;
  309. panel.style.opacity = '1';
  310. panel.style.pointerEvents = 'auto';
  311. }
  312.  
  313. function scoreImage(img) {
  314. const rect = img.getBoundingClientRect();
  315. const w = img.naturalWidth || rect.width || 0;
  316. const h = img.naturalHeight || rect.height || 0;
  317. return w * h;
  318. }
  319.  
  320. function isVariantImage(img) {
  321. if (!(img instanceof HTMLImageElement)) return false;
  322. if (!isVisible(img)) return false;
  323.  
  324. const src = img.currentSrc || img.src || '';
  325. const alt = (img.alt || '').toLowerCase();
  326. if (!src) return false;
  327.  
  328. const looksRelevant =
  329. src.startsWith('data:image/') ||
  330. src.startsWith('blob:') ||
  331. src.startsWith('http') ||
  332. alt.includes('variant');
  333.  
  334. if (!looksRelevant) return false;
  335.  
  336. const w = img.naturalWidth || img.width || 0;
  337. const h = img.naturalHeight || img.height || 0;
  338. return w >= 256 && h >= 256;
  339. }
  340.  
  341. function findImageNearTrigger(trigger) {
  342. let node = trigger.closest('button, a, [role="button"]');
  343.  
  344. for (let i = 0; i < 10 && node; i++, node = node.parentElement) {
  345. const imgs = Array.from(node.querySelectorAll('img')).filter(isVariantImage);
  346. if (imgs.length) {
  347. imgs.sort((a, b) => scoreImage(b) - scoreImage(a));
  348. return imgs[0];
  349. }
  350. }
  351.  
  352. const all = Array.from(document.querySelectorAll('img')).filter(isVariantImage);
  353. all.sort((a, b) => scoreImage(b) - scoreImage(a));
  354. return all[0] || null;
  355. }
  356.  
  357. function loadImage(src) {
  358. return new Promise((resolve, reject) => {
  359. const img = new Image();
  360. img.decoding = 'async';
  361. img.onload = () => resolve(img);
  362. img.onerror = () => reject(new Error('image load failed'));
  363. img.src = src;
  364. });
  365. }
  366.  
  367. function canvasToBlob(canvas, type, quality) {
  368. return new Promise((resolve, reject) => {
  369. canvas.toBlob(blob => {
  370. if (blob) resolve(blob);
  371. else reject(new Error('canvas.toBlob failed'));
  372. }, type, quality);
  373. });
  374. }
  375.  
  376. async function imageSrcToJpegBlob(src) {
  377. const img = await loadImage(src);
  378. const width = img.naturalWidth || img.width;
  379. const height = img.naturalHeight || img.height;
  380.  
  381. if (!width || !height) {
  382. throw new Error('invalid image dimensions');
  383. }
  384.  
  385. const canvas = document.createElement('canvas');
  386. canvas.width = width;
  387. canvas.height = height;
  388.  
  389. const ctx = canvas.getContext('2d', { alpha: false });
  390. ctx.fillStyle = '#ffffff';
  391. ctx.fillRect(0, 0, width, height);
  392. ctx.drawImage(img, 0, 0);
  393.  
  394. return await canvasToBlob(canvas, 'image/jpeg', JPEG_QUALITY);
  395. }
  396.  
  397. function pad2(n) {
  398. return String(n).padStart(2, '0');
  399. }
  400.  
  401. function makeTimestamp() {
  402. const d = new Date();
  403. return `${pad2(d.getDate())}.${pad2(d.getMonth() + 1)}.${d.getFullYear()} ${pad2(d.getHours())}-${pad2(d.getMinutes())}-${pad2(d.getSeconds())}`;
  404. }
  405.  
  406. function sanitizePart(text) {
  407. return String(text || '')
  408. .replace(/[\\/:*?"<>|]+/g, ' ')
  409. .replace(/\s+/g, ' ')
  410. .trim();
  411. }
  412.  
  413. function makeFilename(img) {
  414. return `Grok Console - ${makeTimestamp()}.jpg`;
  415. }
  416.  
  417. function downloadBlob(blob, filename) {
  418. const url = URL.createObjectURL(blob);
  419. const a = document.createElement('a');
  420. a.href = url;
  421. a.download = filename;
  422. a.style.display = 'none';
  423. document.body.appendChild(a);
  424. a.click();
  425. a.remove();
  426.  
  427. setTimeout(() => URL.revokeObjectURL(url), 30000);
  428. }
  429.  
  430. function markBypass(el) {
  431. if (!(el instanceof Element)) return;
  432. el.setAttribute(BYPASS_ATTR, '1');
  433. setTimeout(() => {
  434. try {
  435. el.removeAttribute(BYPASS_ATTR);
  436. } catch {}
  437. }, 300);
  438. }
  439.  
  440. function invokeOriginalDownload(trigger) {
  441. if (!(trigger instanceof HTMLElement)) return;
  442. markBypass(trigger);
  443. trigger.click();
  444. }
  445.  
  446. function isDownloadTrigger(el) {
  447. if (!(el instanceof Element)) return false;
  448.  
  449. const trigger = el.closest('button, a, [role="button"]');
  450. if (!trigger) return false;
  451.  
  452. if (trigger.getAttribute(BYPASS_ATTR) === '1') return false;
  453. if (trigger.id === RESET_BTN_ID || trigger.id === TOGGLE_BTN_ID) return false;
  454. if (trigger.closest(`#${PANEL_ID}`)) return false;
  455.  
  456. const text = getText(trigger);
  457. return /\bdownload\b|\bsave\b/.test(text);
  458. }
  459.  
  460. function installDownloadInterceptor() {
  461. document.addEventListener('click', async (event) => {
  462. const target = event.target;
  463. if (!(target instanceof Element)) return;
  464. if (!isDownloadTrigger(target)) return;
  465.  
  466. const trigger = target.closest('button, a, [role="button"]');
  467. if (!trigger) return;
  468.  
  469. const img = findImageNearTrigger(trigger);
  470. if (!img) {
  471. log('download clicked, but image not found; fallback to native', 'warn');
  472. return;
  473. }
  474.  
  475. const src = img.currentSrc || img.src || '';
  476. if (!src) {
  477. log('image src empty; fallback to native', 'warn');
  478. return;
  479. }
  480.  
  481. event.preventDefault();
  482. event.stopPropagation();
  483. if (typeof event.stopImmediatePropagation === 'function') {
  484. event.stopImmediatePropagation();
  485. }
  486.  
  487. try {
  488. const jpegBlob = await imageSrcToJpegBlob(src);
  489. const filename = makeFilename(img);
  490. downloadBlob(jpegBlob, filename);
  491. log(`saved as jpeg: ${filename}`);
  492. } catch (e) {
  493. log(`jpeg conversion failed, fallback to native: ${e.message}`, 'error');
  494. invokeOriginalDownload(trigger);
  495. }
  496. }, true);
  497. }
  498.  
  499. function initUi() {
  500. createPanel();
  501. positionPanel();
  502.  
  503. const mo = new MutationObserver(() => {
  504. createPanel();
  505. positionPanel();
  506. });
  507.  
  508. mo.observe(document.documentElement, {
  509. childList: true,
  510. subtree: true,
  511. attributes: true,
  512. attributeFilter: ['class', 'style', 'hidden', 'data-state', 'aria-hidden']
  513. });
  514.  
  515. const throttled = (() => {
  516. let raf = 0;
  517. return () => {
  518. cancelAnimationFrame(raf);
  519. raf = requestAnimationFrame(positionPanel);
  520. };
  521. })();
  522.  
  523. window.addEventListener('resize', throttled);
  524. window.addEventListener('scroll', throttled, { passive: true });
  525.  
  526. setTimeout(positionPanel, 150);
  527. setTimeout(positionPanel, 500);
  528. setTimeout(positionPanel, 1200);
  529. }
  530.  
  531. installDownloadInterceptor();
  532.  
  533. if (document.readyState !== 'loading') {
  534. initUi();
  535. } else {
  536. document.addEventListener('DOMContentLoaded', initUi, { once: true });
  537. }
  538.  
  539. log(`ready; 2K forcing is ${isForce2kEnabled() ? 'ON' : 'OFF'}`, 'warn');
  540. })();
Advertisement
Comments
Add Comment
Please, Sign In to add comment