Skip to content

Instantly share code, notes, and snippets.

@Albatrosicks
Last active January 11, 2026 02:26
Show Gist options
  • Select an option

  • Save Albatrosicks/2417e4aee973e1007c1f11df39e7e45d to your computer and use it in GitHub Desktop.

Select an option

Save Albatrosicks/2417e4aee973e1007c1f11df39e7e45d to your computer and use it in GitHub Desktop.
// ==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