Automatically switch to Gemini 3 Pro model and force HD quality thumbnails in Gemini Enterprise
Size
13.7 KB
Version
1.1.1
Created
Jan 16, 2026
Updated
18 days ago
1// ==UserScript==
2// @name Gemini Enterprise HD Thumbnails & Auto 3 Pro
3// @namespace http://tampermonkey.net/
4// @version 1.1.1
5// @description Automatically switch to Gemini 3 Pro model and force HD quality thumbnails in Gemini Enterprise
6// @author schweigen
7// @match https://business.gemini.google/*
8// @run-at document-end
9// @grant none
10// @license MIT
11// @downloadURL https://update.greasyfork.org/scripts/557022/Gemini%20Enterprise%20-%20Auto%20Gemini%203%20Pro.user.js
12// @updateURL https://update.greasyfork.org/scripts/557022/Gemini%20Enterprise%20-%20Auto%20Gemini%203%20Pro.meta.js
13// ==/UserScript==
14(function () {
15 'use strict';
16
17 const TARGET_LABEL = '3 Pro';
18 const LOG_PREFIX = '[GeminiAuto3Pro]';
19
20 function log() {
21 if (typeof console === 'undefined' || !console.log) {
22 return;
23 }
24 var args = Array.prototype.slice.call(arguments);
25 args.unshift(LOG_PREFIX);
26 console.log.apply(console, args);
27 }
28
29 function deepQuerySelector(selector) {
30 function search(root) {
31 if (!root || !root.querySelector) {
32 return null;
33 }
34 var found = root.querySelector(selector);
35 if (found) {
36 return found;
37 }
38 var elements = root.querySelectorAll('*');
39 for (var i = 0; i < elements.length; i++) {
40 var el = elements[i];
41 if (el.shadowRoot) {
42 var fromShadow = search(el.shadowRoot);
43 if (fromShadow) {
44 return fromShadow;
45 }
46 }
47 }
48 return null;
49 }
50 return search(document);
51 }
52
53 function deepQuerySelectorAll(selector) {
54 var collected = [];
55 function search(root) {
56 if (!root || !root.querySelectorAll) {
57 return;
58 }
59 var matches = root.querySelectorAll(selector);
60 for (var i = 0; i < matches.length; i++) {
61 collected.push(matches[i]);
62 }
63 var elements = root.querySelectorAll('*');
64 for (var j = 0; j < elements.length; j++) {
65 var el = elements[j];
66 if (el.shadowRoot) {
67 search(el.shadowRoot);
68 }
69 }
70 }
71 search(document);
72 return collected;
73 }
74
75 function waitForElement(selector, timeoutMs) {
76 return new Promise(function (resolve, reject) {
77 var start = Date.now();
78 log('waitForElement: start polling for', selector);
79
80 function check() {
81 var found = deepQuerySelector(selector);
82 if (found) {
83 log('waitForElement: found:', selector);
84 resolve(found);
85 return;
86 }
87 var elapsed = Date.now() - start;
88 if (elapsed >= timeoutMs) {
89 log('waitForElement: timeout for', selector);
90 reject(new Error('Timeout waiting for selector: ' + selector));
91 return;
92 }
93 setTimeout(check, 400);
94 }
95
96 check();
97 });
98 }
99
100 function findModelSelectorHost() {
101 const candidates = deepQuerySelectorAll(
102 'md-text-button.action-model-selector#model-selector-menu-anchor'
103 );
104 if (!candidates.length) {
105 log('findModelSelectorHost: no candidates');
106 return null;
107 }
108
109 // Prefer the one that actually looks like the main model selector
110 const preferred = candidates.find(function (el) {
111 const text = (el.textContent || '').toLowerCase();
112 return text.includes('auto') || text.includes('gemini');
113 });
114
115 const host = preferred || candidates[candidates.length - 1];
116 log(
117 'findModelSelectorHost: picked host, text=',
118 (host.textContent || '').trim()
119 );
120 return host;
121 }
122
123 function findModelSelectorButton() {
124 const host = findModelSelectorHost();
125 if (!host) {
126 log('findModelSelectorButton: host not found');
127 return null;
128 }
129 const shadowButton = host.shadowRoot && host.shadowRoot.querySelector('button');
130 const button = shadowButton || host;
131 log('findModelSelectorButton: using element:', button.tagName);
132 return button;
133 }
134
135 function isAlreadyOnTarget() {
136 const host = findModelSelectorHost();
137 if (!host) {
138 return false;
139 }
140 const labelText = (host.textContent || '').trim();
141 log('isAlreadyOnTarget: current label =', labelText);
142 return labelText.includes(TARGET_LABEL);
143 }
144
145 function openModelMenuIfNeeded() {
146 const menuElement = deepQuerySelector('md-menu.model-selector-menu');
147 if (menuElement) {
148 const isHidden = menuElement.getAttribute('aria-hidden') === 'true';
149 const hasOpenAttribute = menuElement.hasAttribute('open');
150 if (!isHidden && hasOpenAttribute) {
151 log('openModelMenuIfNeeded: menu already open');
152 return true;
153 }
154 }
155
156 const buttonElement = findModelSelectorButton();
157 if (!buttonElement) {
158 log('openModelMenuIfNeeded: button not found');
159 return false;
160 }
161
162 log('openModelMenuIfNeeded: clicking button to open menu');
163 buttonElement.click();
164 return true;
165 }
166
167 function clickGemini3Pro() {
168 const menuElement = deepQuerySelector('md-menu.model-selector-menu');
169 if (!menuElement) {
170 log('clickGemini3Pro: menu element not found');
171 return false;
172 }
173
174 const menuItems = Array.from(menuElement.querySelectorAll('md-menu-item'));
175 if (!menuItems.length) {
176 log('clickGemini3Pro: no menu items found');
177 return false;
178 }
179
180 const targetItem = menuItems.find(function (itemElement) {
181 const text = (itemElement.textContent || '').trim();
182 log('clickGemini3Pro: checking menu item text =', text);
183 return text.includes(TARGET_LABEL);
184 });
185
186 if (!targetItem) {
187 log('clickGemini3Pro: target item not found for label', TARGET_LABEL);
188 return false;
189 }
190
191 // Prefer clicking the actual <li id="item"> inside the shadow DOM if present
192 var interactiveElement = targetItem;
193 if (targetItem.shadowRoot) {
194 var li = targetItem.shadowRoot.querySelector('#item');
195 if (li) {
196 interactiveElement = li;
197 }
198 }
199
200 log('clickGemini3Pro: clicking target item');
201 interactiveElement.click();
202 return true;
203 }
204
205 function trySelectGemini3ProOnce() {
206 if (isAlreadyOnTarget()) {
207 log('trySelectGemini3ProOnce: already on target');
208 return true;
209 }
210
211 const opened = openModelMenuIfNeeded();
212 if (!opened) {
213 log('trySelectGemini3ProOnce: could not open menu');
214 return false;
215 }
216
217 setTimeout(function () {
218 log('trySelectGemini3ProOnce: attempting to click Gemini 3 Pro');
219 waitForElement('md-menu.model-selector-menu', 10000)
220 .then(function () {
221 clickGemini3Pro();
222 })
223 .catch(function (err) {
224 log('trySelectGemini3ProOnce: menu did not appear in time', err && err.message);
225 });
226 }, 350);
227
228 return true;
229 }
230
231 function bootstrapSelectionWithObserver() {
232 log('bootstrapSelectionWithObserver: start');
233 waitForElement('md-text-button.action-model-selector#model-selector-menu-anchor', 30000)
234 .then(function () {
235 setTimeout(function () {
236 trySelectGemini3ProOnce();
237 }, 50);
238 })
239 .catch(function () {
240 log('bootstrapSelectionWithObserver: waitForElement failed, falling back to single attempt');
241 trySelectGemini3ProOnce();
242 });
243 }
244
245 function removeDisclaimersOnce() {
246 var disclaimers = deepQuerySelectorAll('div.disclaimer');
247 if (!disclaimers || !disclaimers.length) {
248 return false;
249 }
250 for (var i = 0; i < disclaimers.length; i++) {
251 var el = disclaimers[i];
252 try {
253 log(
254 'removeDisclaimersOnce: removing disclaimer with text =',
255 (el.textContent || '').trim()
256 );
257 } catch {
258 // ignore logging failures
259 }
260 if (el && el.parentNode) {
261 el.parentNode.removeChild(el);
262 } else if (el && el.remove) {
263 el.remove();
264 }
265 }
266 return true;
267 }
268
269 function bootstrapDisclaimerRemoval() {
270 log('bootstrapDisclaimerRemoval: start');
271 var attempts = 0;
272 var maxAttempts = 30;
273
274 function tick() {
275 attempts++;
276 var removed = removeDisclaimersOnce();
277 if (removed || attempts >= maxAttempts) {
278 if (removed) {
279 log('bootstrapDisclaimerRemoval: disclaimer(s) removed');
280 } else {
281 log('bootstrapDisclaimerRemoval: no disclaimers found after retries');
282 }
283 return;
284 }
285 setTimeout(tick, 1000);
286 }
287
288 tick();
289 }
290
291 function forceHDThumbnails() {
292 log('forceHDThumbnails: start');
293
294 function replaceWithHD(img) {
295 if (!img || !img.src) {
296 return false;
297 }
298
299 var originalSrc = img.src;
300 var hdSrc = originalSrc;
301
302 // Replace common low-quality thumbnail patterns with HD versions
303 // Pattern 1: =s followed by dimensions (e.g., =s48, =s96)
304 if (hdSrc.includes('=s')) {
305 hdSrc = hdSrc.replace(/=s\d+(-c)?/g, '=s0');
306 log('forceHDThumbnails: replaced =s pattern:', originalSrc, '->', hdSrc);
307 }
308
309 // Pattern 2: =w followed by dimensions (e.g., =w48, =w96)
310 if (hdSrc.includes('=w')) {
311 hdSrc = hdSrc.replace(/=w\d+(-h\d+)?(-c)?/g, '=s0');
312 log('forceHDThumbnails: replaced =w pattern:', originalSrc, '->', hdSrc);
313 }
314
315 // Pattern 3: -rw or -rh followed by dimensions
316 if (hdSrc.includes('-rw') || hdSrc.includes('-rh')) {
317 hdSrc = hdSrc.replace(/-r[wh]\d+/g, '=s0');
318 log('forceHDThumbnails: replaced -rw/-rh pattern:', originalSrc, '->', hdSrc);
319 }
320
321 if (hdSrc !== originalSrc) {
322 img.src = hdSrc;
323 img.srcset = '';
324 img.setAttribute('data-hd-forced', 'true');
325 return true;
326 }
327
328 return false;
329 }
330
331 function processAllImages() {
332 var images = document.querySelectorAll('img:not([data-hd-forced])');
333 var count = 0;
334
335 for (var i = 0; i < images.length; i++) {
336 if (replaceWithHD(images[i])) {
337 count++;
338 }
339 }
340
341 if (count > 0) {
342 log('forceHDThumbnails: processed', count, 'images');
343 }
344
345 return count;
346 }
347
348 // Initial processing
349 processAllImages();
350
351 // Watch for new images being added
352 var observer = new MutationObserver(function(mutations) {
353 var hasNewImages = false;
354
355 for (var i = 0; i < mutations.length; i++) {
356 var mutation = mutations[i];
357
358 if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
359 for (var j = 0; j < mutation.addedNodes.length; j++) {
360 var node = mutation.addedNodes[j];
361
362 if (node.nodeType === 1) { // Element node
363 if (node.tagName === 'IMG') {
364 hasNewImages = true;
365 break;
366 } else if (node.querySelectorAll) {
367 var imgs = node.querySelectorAll('img');
368 if (imgs.length > 0) {
369 hasNewImages = true;
370 break;
371 }
372 }
373 }
374 }
375 }
376
377 if (hasNewImages) break;
378 }
379
380 if (hasNewImages) {
381 processAllImages();
382 }
383 });
384
385 observer.observe(document.body, {
386 childList: true,
387 subtree: true
388 });
389
390 log('forceHDThumbnails: observer installed');
391 }
392
393 log('script loaded');
394 if (typeof window !== 'undefined') {
395 try {
396 window.__GeminiEnterpriseAutoGemini3Pro = true;
397 } catch {
398 // ignore
399 }
400 }
401
402 if (document.readyState === 'complete' || document.readyState === 'interactive') {
403 bootstrapSelectionWithObserver();
404 bootstrapDisclaimerRemoval();
405 forceHDThumbnails();
406 } else {
407 window.addEventListener('DOMContentLoaded', function () {
408 bootstrapSelectionWithObserver();
409 bootstrapDisclaimerRemoval();
410 forceHDThumbnails();
411 });
412 }
413})();