Registers "Ask Deepseek" in the native browser context menu (via Tampermonkey) and adds the Ctrl+Y keyboard shortcut: saves the selected text, opens chat.deepseek.com in a new tab and automatically sends the question with web search enabled.
// ==UserScript==
// @name Ask Deepseek
// @namespace https://chat.deepseek.com/
// @version 1.3.3
// @description Registers "Ask Deepseek" in the native browser context menu (via Tampermonkey) and adds the Ctrl+Y keyboard shortcut: saves the selected text, opens chat.deepseek.com in a new tab and automatically sends the question with web search enabled.
// @match http://*/*
// @match https://*/*
// @match https://chat.deepseek.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_registerMenuCommand
// @license MIT
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
const DEBUG = true;
const STORAGE_KEY = 'pendingQuery';
const DEEPSEEK_HOST = 'chat.deepseek.com';
const UI_TIMEOUT_MS = 15000;
const log = (...a) => console.log('[Ask Deepseek]', ...a);
const warn = (...a) => console.warn('[Ask Deepseek]', ...a);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
/* ------------------------------------------------------------------ */
/* DeepSeek UI selectors (multiple fallbacks) */
/* ------------------------------------------------------------------ */
const INPUT_SELECTORS = [
'textarea#chat-input',
'textarea[data-testid="chat-input"]',
'textarea[data-testid="chat_input"]',
'div[contenteditable="true"][role="textbox"]',
'textarea[placeholder*="Message" i]',
'div[contenteditable="true"]',
'textarea[placeholder]',
];
const SEND_SELECTORS = [
'div[role="button"][aria-label="Send"]',
'button[aria-label="Send"]',
'div[aria-label="Send"]',
'button[data-testid="send-button"]',
'div[role="button"][class*="send" i]',
];
const WEBSEARCH_TOGGLE_SELECTORS = [
'button[aria-label*="Web search" i]',
'[aria-label*="Web search" i]',
'[title*="Web search" i]',
'[data-testid*="web-search" i]',
'[data-testid*="web_search" i]',
'[data-testid*="websearch" i]',
'button[class*="web-search" i]',
'button[class*="web_search" i]',
'[class*="search-toggle" i]',
];
const MODE_DROPDOWN_SELECTORS = [
'button[aria-label*="search mode" i]',
'[data-testid*="search-mode" i]',
'[data-testid*="search-mode-button" i]',
'button[class*="search-mode" i]',
];
const MODE_ITEM_TEXT = ['Web search'];
const MODE_ITEM_SELECTORS = [
'[role="menuitem"]',
'[role="option"]',
'div[class*="menu" i] div[class*="item" i]',
'li[class*="item" i]',
];
function queryFirst(selectors) {
for (const sel of selectors) {
try {
const el = document.querySelector(sel);
if (el) return { el, selector: sel };
} catch (err) {
warn('Invalid selector:', sel, err);
}
}
return null;
}
/* ------------------------------------------------------------------ */
/* PART A - All pages: native context menu entry */
/* The entry is registered natively via GM_registerMenuCommand: */
/* Tampermonkey (4.14+) adds it to the browser's native context */
/* menu. On Firefox, an element injected into the DOM cannot */
/* overlay the native menu, so this is the only robust way. */
/* The entry is always present in the menu: if there is no */
/* selection, clicking it does nothing. */
/* ------------------------------------------------------------------ */
const SELECTION_FRESHNESS_MS = 10000;
let lastSelection = null;
/* Keyboard shortcut (works when the page has focus):
Ctrl+Y. To change it, modify the values below. */
const SHORTCUT_KEY = 'KeyY';
const SHORTCUT_CTRL = true;
const SHORTCUT_SHIFT = false;
function partA() {
document.addEventListener('contextmenu', onContextMenu, true);
document.addEventListener('keydown', onKeyDown, true);
if (typeof GM_registerMenuCommand === 'function') {
GM_registerMenuCommand('\u{1F50D} Ask Deepseek', onMenuClick);
log('Native context menu entry registered');
} else {
warn('GM_registerMenuCommand not available: update Tampermonkey (4.14+)');
}
}
function onKeyDown(e) {
const modsOk = (SHORTCUT_CTRL === !!e.ctrlKey) && (SHORTCUT_SHIFT === !!e.shiftKey) && !e.altKey && !e.metaKey;
if (!modsOk || e.code !== SHORTCUT_KEY) return;
e.preventDefault();
e.stopPropagation();
const raw = window.getSelection ? window.getSelection().toString() : '';
const clean = raw.replace(/\s+/g, ' ').trim();
if (!clean) {
log('Shortcut pressed with no selection: no action');
return;
}
log('Shortcut: selection detected (' + clean.length + ' chars)');
openDeepSeek(clean);
}
function onContextMenu() {
lastSelection = {
text: window.getSelection ? window.getSelection().toString() : '',
time: Date.now(),
};
}
function onMenuClick() {
let raw = '';
if (lastSelection && Date.now() - lastSelection.time < SELECTION_FRESHNESS_MS) {
raw = lastSelection.text;
} else if (window.getSelection) {
raw = window.getSelection().toString();
}
const clean = raw.replace(/\s+/g, ' ').trim();
if (!clean) {
log('No selection: no action');
return;
}
log('Selection detected (' + clean.length + ' chars)');
openDeepSeek(clean);
}
function openDeepSeek(text) {
GM_setValue(STORAGE_KEY, text);
log('Question saved (' + text.length + ' chars)');
window.open('https://chat.deepseek.com/', '_blank', 'noopener');
}
/* ------------------------------------------------------------------ */
/* PART B - chat.deepseek.com: automatic submission */
/* ------------------------------------------------------------------ */
function partB() {
const query = GM_getValue(STORAGE_KEY, '');
GM_deleteValue(STORAGE_KEY);
if (!query || !query.trim()) {
log('No pending question');
return;
}
log('Pending question found, waiting for the chat UI to load...');
waitForChatUI(UI_TIMEOUT_MS).then((ready) => {
if (!ready) return;
if (!setInputText(query)) return;
sleep(500)
.then(enableWebSearch)
.then(() => sleep(500))
.then(sendMessage);
});
}
function waitForChatUI(timeoutMs) {
return new Promise((resolve) => {
const started = Date.now();
const check = () => {
if (queryFirst(INPUT_SELECTORS)) {
log('Chat UI ready');
return resolve(true);
}
if (Date.now() - started > timeoutMs) {
warn('Timeout: DeepSeek UI not ready within ' + timeoutMs + ' ms');
return resolve(false);
}
setTimeout(check, 500);
};
check();
});
}
function setInputText(text) {
const found = queryFirst(INPUT_SELECTORS);
if (!found) {
warn('Message input not found, no submission');
return false;
}
const el = found.el;
el.focus();
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
const proto = el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
const setter = Object.getOwnPropertyDescriptor(proto, 'value').set;
setter.call(el, text);
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
log('Text set (' + text.length + ' chars) via:', found.selector);
} else if (el.isContentEditable) {
try {
document.execCommand('selectAll', false, null);
document.execCommand('insertText', false, text);
} catch (err) {
el.textContent = text;
el.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: text }));
}
log('Text set (' + text.length + ' chars) via:', found.selector);
} else {
warn('Unsupported input type:', el.tagName);
return false;
}
return true;
}
function isToggleActive(el) {
const attr = el.getAttribute('aria-pressed');
if (attr !== null) return attr === 'true';
return /(^|\s)(active|on)(\s|$)/i.test(el.className || '') || (el.getAttribute('data-state') === 'on');
}
async function enableWebSearch() {
const toggle = queryFirst(WEBSEARCH_TOGGLE_SELECTORS);
if (toggle) {
if (isToggleActive(toggle.el)) {
log('Web search already active');
return;
}
toggle.el.click();
await sleep(300);
if (isToggleActive(toggle.el)) {
log('Web search enabled (toggle):', toggle.selector);
return;
}
warn('Toggle clicked but not active:', toggle.selector);
}
const dropdown = queryFirst(MODE_DROPDOWN_SELECTORS);
if (dropdown) {
dropdown.el.click();
await sleep(400);
const items = [];
for (const sel of MODE_ITEM_SELECTORS) {
try {
items.push(...Array.from(document.querySelectorAll(sel)));
} catch (err) {}
}
const target = items.find((i) => MODE_ITEM_TEXT.some((t) => (i.textContent || '').includes(t)));
if (target) {
target.click();
log('Web search enabled (menu item):', target.textContent.trim());
return;
}
warn('"Web search" item not found in the opened menu');
document.body.click();
}
warn('Web search toggle/item not found: sending without web search');
}
function sendMessage() {
const send = queryFirst(SEND_SELECTORS);
if (send) {
send.el.click();
log('Message sent (button):', send.selector);
return;
}
const input = queryFirst(INPUT_SELECTORS);
if (!input) {
warn('No send button and no input: cannot send');
return;
}
input.el.focus();
const opts = { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true };
input.el.dispatchEvent(new KeyboardEvent('keydown', opts));
input.el.dispatchEvent(new KeyboardEvent('keypress', opts));
input.el.dispatchEvent(new KeyboardEvent('keyup', opts));
log('Message sent (Enter)');
}
/* ------------------------------------------------------------------ */
/* Startup */
/* ------------------------------------------------------------------ */
function main() {
if (location.hostname === DEEPSEEK_HOST) {
partB();
} else {
partA();
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', main);
} else {
main();
}
})();