Last active
January 11, 2026 02:26
-
-
Save Albatrosicks/2417e4aee973e1007c1f11df39e7e45d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Search with Gemini | |
| // @namespace https://github.com/alexey-kudryavtsev/ | |
| // @version 2025.07.27.3 | |
| // @description Lets you use Gemini as a custom search engine. Reads 'prompt' and 'model' parameters from URL and automatically submits to Gemini chat. | |
| // @author Alexey Kudryavtsev | |
| // @match https://gemini.google.com/* | |
| // @grant none | |
| // @run-at document-start | |
| // @license Apache-2.0 | |
| // @homepageURL https://github.com/alexey-kudryavtsev/search-with-gemini | |
| // @supportURL https://github.com/alexey-kudryavtsev/search-with-gemini/issues | |
| // @updateURL https://gist.github.com/Albatrosicks/2417e4aee973e1007c1f11df39e7e45d/raw/search-with-gemini.user.js | |
| // @downloadURL https://gist.github.com/Albatrosicks/2417e4aee973e1007c1f11df39e7e45d/raw/search-with-gemini.user.js | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| // --- Configuration --- | |
| const log = (message) => console.log(`[Gemini Userscript] ${message}`); | |
| const SELECTORS = { | |
| PROMPT_INPUT: '.ql-editor[contenteditable="true"]', | |
| SUBMIT_BUTTON: 'button[aria-label="Send message"]', | |
| // Note: We target the specific button inside the trigger container for better click reliability | |
| MODEL_MENU_TRIGGER_BTN: 'div[data-test-id="bard-mode-menu-button"] button', | |
| MODEL_OPTIONS: { | |
| fast: 'button[data-test-id="bard-mode-option-fast"]', | |
| thinking: 'button[data-test-id="bard-mode-option-thinking"]', | |
| pro: 'button[data-test-id="bard-mode-option-pro"]' | |
| } | |
| }; | |
| log('Script initialized (document-start).'); | |
| if (typeof window.geminiScript === 'undefined') { | |
| window.geminiScript = {}; | |
| } | |
| // --- Early Execution: Extract params --- | |
| const currentUrl = new URL(window.location.href); | |
| const promptValue = currentUrl.searchParams.get('prompt'); | |
| const modelValue = currentUrl.searchParams.get('model'); | |
| if (promptValue || modelValue) { | |
| if (promptValue) { | |
| window.geminiScript.currentPrompt = promptValue; | |
| currentUrl.searchParams.delete('prompt'); | |
| } | |
| if (modelValue) { | |
| window.geminiScript.targetModel = modelValue.toLowerCase(); | |
| currentUrl.searchParams.delete('model'); | |
| } | |
| const cleanUrl = currentUrl.pathname + currentUrl.search + currentUrl.hash; | |
| history.replaceState(null, '', cleanUrl); | |
| } | |
| // --- Helpers --- | |
| function wait(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| // Simulates a full "human" click (MouseDown -> MouseUp -> Click) | |
| // This is often required for Angular/Material buttons to register the action. | |
| function simulateClick(element) { | |
| ['mousedown', 'mouseup', 'click'].forEach(eventType => { | |
| element.dispatchEvent(new MouseEvent(eventType, { | |
| bubbles: true, | |
| cancelable: true, | |
| view: window | |
| })); | |
| }); | |
| } | |
| function waitForElement(selector, root = document.body) { | |
| return new Promise((resolve) => { | |
| const el = root.querySelector(selector); | |
| if (el) return resolve(el); | |
| const observer = new MutationObserver((mutations, obs) => { | |
| const el = root.querySelector(selector); | |
| if (el) { | |
| obs.disconnect(); | |
| resolve(el); | |
| } | |
| }); | |
| observer.observe(root, { childList: true, subtree: true }); | |
| }); | |
| } | |
| // --- Logic --- | |
| async function selectModel(modelName) { | |
| if (!modelName || !SELECTORS.MODEL_OPTIONS[modelName]) return; | |
| log(`βοΈ Switching model to: ${modelName}`); | |
| // 1. Find and click the menu trigger | |
| const triggerBtn = await waitForElement(SELECTORS.MODEL_MENU_TRIGGER_BTN); | |
| simulateClick(triggerBtn); | |
| // 2. Wait for the overlay to appear (give it a slight buffer) | |
| await wait(300); | |
| // 3. Find the specific option button | |
| const optionSelector = SELECTORS.MODEL_OPTIONS[modelName]; | |
| // Note: The menu usually renders in a CDK overlay at the root of body | |
| const optionBtn = await waitForElement(optionSelector, document.body); | |
| // 4. Click the option | |
| log(`β Clicking option: ${modelName}`); | |
| simulateClick(optionBtn); | |
| // 5. Wait for menu to close/UI to settle | |
| await wait(500); | |
| } | |
| async function prefillThenSubmit(textToFill) { | |
| log('π΅οΈββοΈ Waiting for input field...'); | |
| const editorDiv = await waitForElement(SELECTORS.PROMPT_INPUT); | |
| // Target the <p> inside the editor for text insertion, or the editor itself | |
| const pTag = editorDiv.querySelector('p'); | |
| const targetElement = pTag || editorDiv; | |
| log('β Input field found. Injecting text...'); | |
| // 1. Focus the editor | |
| editorDiv.focus(); | |
| // 2. Set text | |
| targetElement.textContent = textToFill; | |
| // 3. IMPORTANT: Dispatch 'input' event so the framework sees the change | |
| // This unlocks the send button internally | |
| editorDiv.dispatchEvent(new InputEvent('input', { | |
| bubbles: true, | |
| inputType: 'insertText', | |
| data: textToFill | |
| })); | |
| log('π Text injected and input event fired.'); | |
| // 4. Wait for submit button to be enabled | |
| const submitButton = await waitForElement(SELECTORS.SUBMIT_BUTTON); | |
| // Polling to ensure button is actually enabled (sometimes takes a ms after input event) | |
| let attempts = 0; | |
| const checkAndClick = setInterval(() => { | |
| attempts++; | |
| if (!submitButton.disabled) { | |
| clearInterval(checkAndClick); | |
| log('π Submit button enabled. Clicking...'); | |
| simulateClick(submitButton); | |
| } else if (attempts > 20) { // 2 seconds timeout | |
| clearInterval(checkAndClick); | |
| log('β Submit button never became enabled.'); | |
| } | |
| }, 100); | |
| } | |
| // --- Main --- | |
| document.addEventListener('DOMContentLoaded', async () => { | |
| log('DOM Ready.'); | |
| const { currentPrompt, targetModel } = window.geminiScript; | |
| // Execute sequentially | |
| if (targetModel) { | |
| await selectModel(targetModel); | |
| } | |
| if (currentPrompt) { | |
| await prefillThenSubmit(currentPrompt); | |
| } | |
| }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment