Ask Deepseek

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.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu betiği yüklemek için bir betik yöneticisi eklentisi yüklemeniz gerekecektir.

(Zaten bir betik yöneticim var, hadi yükleyelim!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==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();
  }
})();