DeepSeek Toolkit - Core

Core framework for DeepSeek chat enhancements. Provides plugin API, wide chat view, and anti-recall protection.

Από την 09/11/2025. Δείτε την τελευταία έκδοση.

Αυτός ο κώδικας δεν πρέπει να εγκατασταθεί άμεσα. Είναι μια βιβλιοθήκη για άλλους κώδικες που περιλαμβάνεται μέσω της οδηγίας meta // @require https://update.greasyfork.org/scripts/555353/1692458/DeepSeek%20Toolkit%20-%20Core.js

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==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 || '<svg width="14" height="14"><circle cx="7" cy="7" r="5" fill="currentColor"/></svg>';
            const nameText = config.name || config.id;

            button.innerHTML = `
                <div class="ds-icon ds-atom-button__icon" style="font-size: 14px; width: 14px; height: 14px; margin-right: 0px;">
                    ${iconSvg}
                </div>
                <span class=""><span class="_6dbc175">${escapeHtml(nameText)}</span></span>
            `;

            // 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 || '<svg width="16" height="16"><circle cx="8" cy="8" r="6" fill="currentColor"/></svg>';

            button.innerHTML = `
                <div class="ds-icon-button__hover-bg"></div>
                <div class="ds-icon" style="font-size: 16px; width: 16px; height: 16px;">
                    ${iconSvg}
                </div>
            `;

            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);
    });

})();