Add a spark of magic to your browsing. Background fetch page content with Ctrl+Middle Click or Alt+G, and seamlessly send it to Gemini with automatic model switching.
// ==UserScript==
// @name ✨ Gemini Key: One-Click Web Intelligence & Spark Magic
// @name:en ✨ Gemini Key: One-Click Web Intelligence & Spark Magic
// @name:zh-TW ✨ Gemini Key:一鍵網頁文章傳送與智慧火花
// @name:zh-CN ✨ Gemini Key:一键网页文章传送与智慧火花
// @name:ja ✨ Gemini Key: ワンクリックウェブ記事転送&スパークマジック
// @name:ko ✨ Gemini Key: 원클릭 웹 문서 전송 및 스파크 매직
// @name:es ✨ Gemini Key: Inteligencia web con un clic y magia Spark
// @author WellsTsai
// @namespace wellstsai.com
// @version 2026.5.20.1
// @description Add a spark of magic to your browsing. Background fetch page content with Ctrl+Middle Click or Alt+G, and seamlessly send it to Gemini with automatic model switching.
// @description:en Add a spark of magic to your browsing. Background fetch page content with Ctrl+Middle Click or Alt+G, and seamlessly send it to Gemini with automatic model switching.
// @description:zh-TW 為您的瀏覽體驗注入智慧魔法。按住 Ctrl+中鍵或 Alt+G 鍵,即可在背景抓取網頁內容並自動切換模型傳送至 Gemini 進行深度分析。
// @description:zh-CN 为您的浏览体验注入智慧魔法。按住 Ctrl+中键或 Alt+G 键,即可在背景抓取网页内容并自动切换模型传送至 Gemini 进行深度分析。
// @description:ja ネットサーフィンにスパークマジックを。Ctrl+中クリックまたはAlt+Gキーでコンテンツを抽出し、自動モデル切り替え機能でGeminiに送信して要約・分析します。
// @description:ko 브라우징에 스파크 매직을 더해보세요. Ctrl+휠 클릭 또는 Alt+G로 본문을 백그라운드에서 추출하여 모델 자동 전환과 함께 Gemini로 전송합니다.
// @description:es Añade un toque de magia Spark a tu navegación. Extrae contenido en segundo plano con Ctrl+Clic Central o Alt+G, y envíalo a Gemini con cambio de modelo automático.
// @match *://*/*
// @grant GM_openInTab
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_registerMenuCommand
// @connect *
// @license MIT
// ==/UserScript==
(function() {
'use strict';
if (window.top !== window.self) return;
// ==========================================
// [ Configurations & Constants ]
// ==========================================
const CONFIG = {
MAX_LEN: 25000,
MIN_LEN: 100,
TARGET_IDX: 1, // Target model index (1-based: 1 represents the first option in the menu)
KEY: 'pending_gemini_prompt',
GEMINI_URL: 'https://gemini.google.com'
};
const SELECTORS = {
READY: 'h1[data-test-id="message"], .zero-state-container',
MENU_BTN: 'button[data-test-id="bard-mode-menu-button"]',
MENU_ITEM: 'gem-menu-item',
EDITOR: '.ql-editor[contenteditable="true"], div[contenteditable="true"]',
SEND_BTN: 'button:has(mat-icon[data-mat-icon-name="arrow_upward"]), button:has(mat-icon[data-mat-icon-name="send"])'
};
// ==========================================
// [ Utility Helpers ]
// ==========================================
const sleep = ms => new Promise(r => setTimeout(r, ms));
// Lightweight DOM observer targeting a specific CSS selector
const waitEl = (sel, timeout = 10000) => new Promise((res, rej) => {
let el = document.querySelector(sel);
if (el) return res(el);
const obs = new MutationObserver(() => {
if (el = document.querySelector(sel)) { obs.disconnect(); res(el); }
});
obs.observe(document, { childList: true, subtree: true });
setTimeout(() => { obs.disconnect(); rej(new Error(`Timeout: ${sel}`)); }, timeout);
});
// High-fidelity click simulator designed to bypass Angular/Material MDC event boundaries
const click = el => {
if (!el) return;
el.focus?.();
const win = el.ownerDocument?.defaultView || window;
for (const type of ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click']) {
const Cls = type.startsWith('pointer') ? PointerEvent : MouseEvent;
const init = { bubbles: true, cancelable: true, composed: true, detail: 1, buttons: type.includes('down') ? 1 : 0 };
try {
el.dispatchEvent(new Cls(type, { ...init, view: win }));
} catch {
el.dispatchEvent(new Cls(type, init)); // Fallback mode for restricted sandboxed environments
}
}
};
const cleanText = root => {
const clone = root.cloneNode(true);
clone.querySelectorAll('script, style, noscript, nav, header, footer, aside, iframe, .ads, .menu').forEach(el => el.remove());
return clone.innerText.replace(/\n\s*\n/g, '\n').replace(/\s{2,}/g, ' ').trim();
};
const sendToGemini = (title, url, text) => {
let promptText = text;
if (promptText.length > CONFIG.MAX_LEN) {
promptText = promptText.substring(0, CONFIG.MAX_LEN) + '\n\n...(文章過長,已自動截斷)';
}
const finalPrompt = `請忽略所有客套話與開場白。這是一篇網路文章,請幫我萃取精華,但【絕對不要過度簡化而破壞了作者原本的思路與邏輯脈絡】,使用台灣正體中文,豐富地說明呈現。\n\n` +
`請以排版清晰、易於掃讀的方式,提供以下三點資訊:\n` +
`1. 🎯【一句話總結】:精準點出這篇文章到底在說什麼、或解決什麼問題。\n` +
`2. 📝【重點解析】:條列文章中的全部關鍵資訊。\n` +
` - 如果是新聞/科技:列出新功能、規格、影響或重點數據。\n` +
` - 如果是論壇/討論:列出樓主痛點、網友主要共識或正反意見。\n` +
` - 如果是評測/食記:列出優缺點、特色或踩雷警告。\n` +
`3. 💡【結論與後續】:文章最終的結論、解決方案,或是值得讀者注意的事項。\n\n` +
`【文章標題】:${title}\n【來源網址】:${url}\n【原文內容】:\n${promptText}`;
GM_setValue(CONFIG.KEY, finalPrompt);
GM_openInTab(`${CONFIG.GEMINI_URL}/app`, { active: false, insert: true, setParent: true });
console.log('[Universal->Gemini] Task prepared. New Gemini tab spawned.');
};
// ==========================================
// [ Phase 1 ] Source Page Logic
// ==========================================
const initSource = () => {
const fetchAndSend = (title, url) => {
if (!url || url.startsWith('javascript:')) return;
document.body.style.cursor = 'wait';
GM_xmlhttpRequest({
method: "GET", url,
onload: ({ responseText }) => {
document.body.style.cursor = 'default';
const doc = new DOMParser().parseFromString(responseText, "text/html");
const text = cleanText(doc.body);
if (text.length < CONFIG.MIN_LEN) return alert('⚠️ 抓取到的文字過少,請直接進入網頁使用 Alt+G。');
sendToGemini(title || doc.title || 'Untitled Article', url, text);
},
onerror: () => {
document.body.style.cursor = 'default';
alert('Failed to retrieve article content.');
}
});
};
const sendCurrent = () => sendToGemini(document.title, location.href, cleanText(document.body));
GM_registerMenuCommand('✨ 摘要當前網頁並傳送至 Gemini (Alt+G)', sendCurrent);
window.addEventListener('keydown', e => {
if (e.altKey && e.key.toLowerCase() === 'g') {
e.preventDefault(); sendCurrent();
}
});
const onMiddleClick = e => {
if (e.button === 1 && e.ctrlKey) {
const a = e.target.closest('a');
if (a?.href) {
e.preventDefault(); e.stopPropagation();
if (e.type === 'auxclick') fetchAndSend(a.innerText.trim() || a.title || 'Untitled Link', a.href);
}
}
};
window.addEventListener('mousedown', onMiddleClick);
window.addEventListener('auxclick', onMiddleClick);
};
// ==========================================
// [ Phase 2 ] Gemini Page Logic
// ==========================================
const initGemini = async () => {
const prompt = GM_getValue(CONFIG.KEY);
if (!prompt) return;
GM_deleteValue(CONFIG.KEY);
console.log('[Universal->Gemini] Pending task detected. Waiting for page to load...');
try {
await waitEl(SELECTORS.READY);
await sleep(300);
} catch (e) {
console.warn('[Universal->Gemini] Welcome UI timeout. Proceeding with execution...', e);
}
// Switch Model
try {
const trigger = await waitEl(SELECTORS.MENU_BTN);
click(trigger);
await sleep(200); // Yield execution to allow transition of menu items
await waitEl(SELECTORS.MENU_ITEM);
const items = document.querySelectorAll(SELECTORS.MENU_ITEM);
const target = items[CONFIG.TARGET_IDX - 1];
if (target) {
click(target);
console.log(`[Universal->Gemini] Model switched successfully.`);
} else {
click(document.body); // Attempt to dismiss potential stray overlays
}
await sleep(500);
} catch (e) {
console.warn("[Universal->Gemini] Model switching process failed or timed out", e);
}
// Inject Prompt & Dispatch
try {
const editor = await waitEl(SELECTORS.EDITOR);
editor.focus();
document.execCommand('insertText', false, prompt);
editor.dispatchEvent(new Event('input', { bubbles: true }));
await sleep(800);
const sendBtn = await waitEl(SELECTORS.SEND_BTN);
click(sendBtn);
console.log('[Universal->Gemini] Message dispatched automatically.');
} catch (e) {
console.error("[Universal->Gemini] Text injection or dispatch process failed", e);
}
};
if (location.origin === CONFIG.GEMINI_URL) {
initGemini();
} else {
initSource();
}
})();