// ==UserScript== // @name DeepSeek Toolkit - Core // @namespace http://tampermonkey.net/ // @version 6.0.0 // @description Core framework for DeepSeek chat enhancements. Provides plugin API, wide chat view, and anti-recall protection. // @author okagame // @match https://chat.deepseek.com/* // @grant none // @icon https://www.google.com/s2/favicons?sz=64&domain=deepseek.com // @license MIT // @run-at document-start // @noframes // ==/UserScript== (function() { 'use strict'; console.log('[DeepSeek Toolkit Core] v6.0.0 initializing...'); // ==================== UTILITY FUNCTIONS ==================== function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function waitForElement(selector, timeout = 10000) { return new Promise((resolve) => { const existing = document.querySelector(selector); if (existing) { resolve(existing); return; } const observer = new MutationObserver(() => { const element = document.querySelector(selector); if (element) { observer.disconnect(); resolve(element); } }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { observer.disconnect(); resolve(null); }, timeout); }); } // ==================== WIDE CHAT MODULE ==================== const WideChat = { init() { // Inject CSS for wide chat view const style = document.createElement('style'); style.textContent = ` /* Wide chat container - target by textarea placeholder */ div:has(> div > textarea[placeholder*="DeepSeek"]), div:has(> textarea[placeholder*="DeepSeek"]), div:has(> div > textarea[placeholder*="Message"]), div:has(> textarea[placeholder*="Message"]), div:has(> div > textarea[placeholder*="消息"]), div:has(> textarea[placeholder*="消息"]) { width: 95% !important; max-width: 95vw !important; } :root { --message-list-max-width: calc(100% - 20px) !important; --chat-max-width: 95vw !important; } [data-message-id] { max-width: 95% !important; } main [class*="mx-auto"], main [class*="max-w"] { max-width: 95% !important; } pre > div[class*="overflow"] { max-height: 50vh; overflow-y: auto; } textarea[placeholder*="DeepSeek"], textarea[placeholder*="Message"], textarea[placeholder*="消息"] { max-width: 100% !important; } `; document.head.appendChild(style); // Apply enforcement this.enforce(); // Setup observer const observer = new MutationObserver(() => this.enforce()); observer.observe(document.body, { childList: true, subtree: true }); console.log('[Wide Chat] Initialized'); }, enforce() { const textarea = document.querySelector('textarea[placeholder*="DeepSeek"]') || document.querySelector('textarea[placeholder*="Message"]') || document.querySelector('textarea[placeholder*="消息"]'); if (textarea) { let element = textarea; for (let i = 0; i < 3 && element; i++) { element.style.width = '95%'; element.style.maxWidth = '95vw'; element = element.parentElement; } } document.querySelectorAll('[data-message-id]').forEach(msgEl => { msgEl.style.maxWidth = '95%'; const parent = msgEl.parentElement; if (parent) parent.style.maxWidth = '95vw'; }); } }; // ==================== ANTI-RECALL MODULE ==================== const AntiRecall = { init() { // SSE parser class SSE { static parse(text) { return text.trimEnd() .split('\n\n') .map(event => event.split('\n')) .map(fields => fields.map(field => [...this.split(field, ': ', 2)])) .map(fields => Object.fromEntries(fields)); } static stringify(events) { return events.map(event => Object.entries(event)) .map(fields => fields.map(field => field.join(': '))) .map(fields => fields.join('\n')) .join('\n\n') + '\n\n'; } static *split(text, separator, limit) { let lastI = 0; for (let separatorI = text.indexOf(separator), n = 1; separatorI !== -1 && n < limit; separatorI = text.indexOf(separator, separatorI + separator.length), n++) { yield text.slice(lastI, separatorI); lastI = separatorI + separator.length; } yield text.slice(lastI); } } // State management for recalled messages class DSState { constructor() { this.fields = {}; this.sessId = ""; this.locale = "en_US"; this.recalled = false; this._updatePath = ""; this._updateMode = "SET"; } update(data) { let precheck = this.preCheck(data); if (data.p) this._updatePath = data.p; if (data.o) this._updateMode = data.o; let value = data.v; if (typeof value === 'object' && this._updatePath === "") { for (let key in value) this.fields[key] = value[key]; return precheck; } this.setField(this._updatePath, value, this._updateMode); return precheck; } preCheck(data) { let path = data.p || this._updatePath; let mode = data.o || this._updateMode; let modified = false; if (mode === "BATCH" && path === "response") { for (let i = 0; i < data.v.length; i++) { let v = data.v[i]; if (v.p === "fragments" && v.v[0].type === "TEMPLATE_RESPONSE") { this.recalled = true; modified = true; const key = `deleted-chat-sess-${this.sessId}-msg-${this.fields.response.message_id}`; localStorage.setItem(key, JSON.stringify(this.fields.response.fragments)); const recallTip = this.locale === "zh_CN" ? "⚠️ 此回复已被撤回,仅在本浏览器存档" : "⚠️ This response has been recalled and archived only on this browser"; data.v[i] = { "v": [{ "id": this.fields.response.fragments.length + 1, "type": "TIP", "style": "WARNING", "content": recallTip }], "p": "fragments", "o": "APPEND" }; } } } return modified ? JSON.stringify(data) : ""; } setField(path, value, mode) { if (mode === "BATCH") { let subMode = "SET"; for (let v of value) { if (v.o) subMode = v.o; this.setField(path + "/" + v.p, v.v, subMode); } } else if (mode === "SET") { this._setValueByPath(this.fields, path, value, false); } else if (mode === "APPEND") { this._setValueByPath(this.fields, path, value, true); } } _setValueByPath(obj, path, value, isAppend) { const keys = path.split("/"); let current = obj; for (let i = 0; i < keys.length - 1; i++) { let key = keys[i].match(/^\d+$/) ? parseInt(keys[i]) : keys[i]; if (!(key in current)) { const nextKey = keys[i + 1].match(/^\d+$/) ? parseInt(keys[i + 1]) : keys[i + 1]; current[key] = typeof nextKey === 'number' ? [] : {}; } current = current[key]; } const lastKey = keys[keys.length - 1].match(/^\d+$/) ? parseInt(keys[keys.length - 1]) : keys[keys.length - 1]; if (isAppend) { if (Array.isArray(current[lastKey])) { current[lastKey].push(...value); } else { current[lastKey] = (current[lastKey] || "") + value; } } else { current[lastKey] = value; } } } // Install XHR hook const originalResponse = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "response"); const originalResponseText = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText"); function handleResponse(req, res) { if (!req._url) return res; const [url] = req._url.split("?"); // Handle history messages if (url === '/api/v0/chat/history_messages') { try { let json = JSON.parse(res); if (!json.data?.biz_data) return res; const data = json.data.biz_data; const sessId = data.chat_session.id; const locale = req._reqHeaders?.["x-client-locale"] || "en_US"; let modified = false; for (let msg of data.chat_messages) { if (msg.status === "CONTENT_FILTER") { const key = `deleted-chat-sess-${sessId}-msg-${msg.message_id}`; const cached = localStorage.getItem(key); if (cached) { msg.fragments = JSON.parse(cached); const recallTip = locale === "zh_CN" ? "⚠️ 此回复已被撤回,仅在本浏览器存档" : "⚠️ This response has been recalled and archived only on this browser"; msg.fragments.push({ "id": msg.fragments.length + 1, "type": "TIP", "style": "WARNING", "content": recallTip }); } else { const notFoundMsg = locale === "zh_CN" ? "⚠️ 此回复已被撤回,且无法在本地缓存中找到" : "⚠️ This response has been recalled and cannot be found in local cache"; msg.fragments = [{ "content": notFoundMsg, "id": 2, "type": "TEMPLATE_RESPONSE" }]; } msg.status = "FINISHED"; modified = true; } } if (modified) return JSON.stringify(json); } catch (e) { console.error('[Anti-Recall] Error:', e); } return res; } // Handle completion streams const streamEndpoints = [ '/api/v0/chat/completion', '/api/v0/chat/edit_message', '/api/v0/chat/regenerate', '/api/v0/chat/continue', '/api/v0/chat/resume_stream' ]; if (!streamEndpoints.includes(url)) return res; if (!req.__dsState) { req.__dsState = new DSState(); req.__messagesCount = 0; if (req._data) { const json = JSON.parse(req._data); req.__dsState.sessId = json.chat_session_id; } if (req._reqHeaders?.["x-client-locale"]) { req.__dsState.locale = req._reqHeaders["x-client-locale"]; } } const messages = res.split("\n"); const lastCount = req.__messagesCount; for (let i = lastCount; i < messages.length - 1; i++) { let msg = messages[i]; req.__messagesCount++; if (!msg.startsWith("data: ")) continue; try { const data = JSON.parse(msg.replace("data:", "")); const modified = req.__dsState.update(data); if (modified) messages[i] = "data: " + modified; } catch (e) { continue; } } if (req.__dsState.recalled) { return messages.join("\n"); } return res; } Object.defineProperty(XMLHttpRequest.prototype, "response", { get: function() { let resp = originalResponse.get.call(this); return handleResponse(this, resp); }, set: originalResponse.set }); Object.defineProperty(XMLHttpRequest.prototype, "responseText", { get: function() { let resp = originalResponseText.get.call(this); return handleResponse(this, resp); }, set: originalResponseText.set }); console.log('[Anti-Recall] XHR hook installed'); } }; // ==================== PLUGIN FRAMEWORK ==================== window.DeepSeekToolkit = { version: '6.0.0', tools: [], actionBar: null, iconButtonArea: null, initialized: false, registerTool(config) { // Validate required fields if (!config || !config.id || !config.type || !config.style) { console.error('[Toolkit] Registration failed: missing required fields', config); return false; } // Validate type/style combination if (config.type === 'toggle' && config.style !== 'toggle-button') { console.error('[Toolkit] Toggle tools must use style: "toggle-button"', config); return false; } if (config.type === 'action' && config.style !== 'icon-button') { console.error('[Toolkit] Action tools must use style: "icon-button"', config); return false; } // Check for duplicates if (this.tools.find(t => t.id === config.id)) { console.warn('[Toolkit] Tool already registered:', config.id); return false; } // Add to registry this.tools.push(config); console.log('[Toolkit] Tool registered:', config.id); // Inject if already initialized if (this.initialized) { this._injectTool(config); } return true; }, async init() { if (this.initialized) return; console.log('[Toolkit] Initializing plugin framework...'); // Wait for action bar this.actionBar = await waitForElement('.ec4f5d61'); if (!this.actionBar) { console.error('[Toolkit] Action bar not found, retrying...'); setTimeout(() => this.init(), 2000); return; } // Find icon button area this.iconButtonArea = this.actionBar.querySelector('.bf38813a'); this.initialized = true; // Inject all registered tools this.tools.forEach(tool => this._injectTool(tool)); // Setup observer for re-injection this._setupObserver(); console.log('[Toolkit] Framework initialized'); }, _injectTool(config) { if (!this.actionBar) { console.warn('[Toolkit] Cannot inject: action bar not ready'); return; } // Check if already injected if (document.getElementById(`toolkit-${config.id}`)) { return; } if (config.style === 'toggle-button') { this._injectToggleButton(config); } else if (config.style === 'icon-button') { this._injectIconButton(config); } }, _injectToggleButton(config) { const button = document.createElement('button'); button.role = 'button'; button.setAttribute('aria-disabled', 'false'); button.className = 'ds-atom-button f79352dc ds-toggle-button ds-toggle-button--md'; button.style.transform = 'translateZ(0px)'; button.tabIndex = 0; button.id = `toolkit-${config.id}`; button.title = config.name || config.id; const iconSvg = config.icon || ''; const nameText = config.name || config.id; button.innerHTML = `
${escapeHtml(nameText)} `; // Set initial state if (config.initialState && config.initialState.enabled) { button.classList.add('ds-toggle-button--selected'); } // Add click handler button.addEventListener('click', () => { const isEnabled = button.classList.toggle('ds-toggle-button--selected'); if (config.onToggle) { try { config.onToggle(isEnabled); } catch (e) { console.error(`[Toolkit] Tool ${config.id} error:`, e); } } }); // Insert after Search button const searchButton = Array.from(this.actionBar.querySelectorAll('.ds-toggle-button')) .find(btn => btn.textContent.includes('Search') || btn.textContent.includes('搜索')); if (searchButton) { searchButton.parentNode.insertBefore(button, searchButton.nextSibling); } else { this.actionBar.insertBefore(button, this.actionBar.firstChild); } }, _injectIconButton(config) { if (!this.iconButtonArea) { console.warn('[Toolkit] Icon button area not found'); return; } const wrapper = document.createElement('div'); wrapper.className = 'bf38813a'; wrapper.dataset.toolkitId = config.id; const button = document.createElement('div'); button.className = 'ds-icon-button f02f0e25'; button.style.cssText = '--hover-size: 34px; width: 34px; height: 34px;'; button.tabIndex = 0; button.role = 'button'; button.setAttribute('aria-disabled', 'false'); button.id = `toolkit-${config.id}`; button.title = config.name || config.id; const iconSvg = config.icon || ''; button.innerHTML = ` `; button.addEventListener('click', () => { if (config.onClick) { try { config.onClick(); } catch (e) { console.error(`[Toolkit] Tool ${config.id} error:`, e); } } }); wrapper.appendChild(button); this.iconButtonArea.parentNode.insertBefore(wrapper, this.iconButtonArea); }, _setupObserver() { const observer = new MutationObserver(() => { const missingTools = this.tools.filter(tool => !document.getElementById(`toolkit-${tool.id}`) ); if (missingTools.length > 0) { missingTools.forEach(tool => this._injectTool(tool)); } }); observer.observe(document.body, { childList: true, subtree: true }); } }; // ==================== INITIALIZATION ==================== async function initialize() { // Wait for DOM if (document.readyState === 'loading') { await new Promise(resolve => { document.addEventListener('DOMContentLoaded', resolve, { once: true }); }); } // Initialize core modules WideChat.init(); AntiRecall.init(); // Initialize plugin framework await window.DeepSeekToolkit.init(); console.log('[DeepSeek Toolkit Core] Fully initialized'); } // Start initialize().catch(error => { console.error('[Toolkit] Initialization failed:', error); setTimeout(initialize, 3000); }); })();