豆包自动发送助手,添加q=?查询参数

自动填充URL中的q参数到豆包聊天框并发送

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

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

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

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

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

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

(I already have a user script manager, let me install it!)

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.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        豆包自动发送助手,添加q=?查询参数
// @namespace   Violentmonkey Scripts
// @match       https://www.doubao.com/chat/*
// @grant       none
// @license MIT
// @version     1.10
// @author      Doubao Assistant
// @description 自动填充URL中的q参数到豆包聊天框并发送
// ==/UserScript==

/*
 * 功能说明:
 * 1. 自动提取URL中的q参数
 * 2. 智能填充到豆包聊天输入框
 * 3. 模拟真实用户输入行为
 * 4. 自动发送消息
 */

async function addDoubaoQueryParam() {
    // 获取URL中的q参数
    const query = new URLSearchParams(window.location.search).get('q');
    if (!query) {
        console.log('URL中未检测到q参数,脚本终止');
        return;
    }

    console.log(`检测到查询参数:${query}`);

    // 元素等待函数
    const waitForElement = async (selectors, timeout = 10000) => {
        const selectorList = Array.isArray(selectors) ? selectors : [selectors];

        while (timeout > 0) {
            for (const selector of selectorList) {
                const element = document.querySelector(selector);
                if (element) {
                    console.log(`通过选择器 "${selector}" 定位到元素`);
                    return element;
                }
            }
            await delay(300);
            timeout -= 300;
        }

        throw new Error(`所有选择器均未定位到元素:${selectorList.join(' / ')}`);
    };

    // 延时函数
    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    // 注入式输入内容
    async function injectText(element, text) {
        console.log(`开始注入内容,长度:${text.length} 字符`);

        // 分块注入
        const chunks = splitTextIntoChunks(text);

        for (let i = 0; i < chunks.length; i++) {
            const chunk = chunks[i];

            // 使用不同的方法注入内容
            if (chunk.type === 'normal') {
                setNativeValue(element, chunk.content);
            } else if (chunk.type === 'html') {
                if (element.insertAdjacentHTML) {
                    element.insertAdjacentHTML('beforeend', chunk.content);
                } else {
                    setNativeValue(element, chunk.content);
                }
            } else if (chunk.type === 'paste') {
                simulatePaste(element, chunk.content);
            }

            // 触发必要的事件
            triggerInputEvents(element);

            // 等待页面响应
            await delay(150 + Math.random() * 100);

            // 检查内容是否被清空
            if (element.value.length < chunk.content.length) {
                console.warn('警告:内容被部分清空,尝试恢复');
                if (Math.random() > 0.5) {
                    setNativeValue(element, element.value + chunk.content);
                } else {
                    simulatePaste(element, chunk.content);
                }
                triggerInputEvents(element);
                await delay(200);
            }

            // 模拟思考时间
            if (i < chunks.length - 1) {
                await delay(300 + Math.random() * 400);
            }
        }

        // 触发最终事件
        element.dispatchEvent(new Event('change', { bubbles: true }));
        element.dispatchEvent(new Event('blur', { bubbles: true }));

        console.log(`内容注入完成:${text}`);
    }

    // 将文本分成不同类型的块
    function splitTextIntoChunks(text) {
        const chunks = [];
        const maxChunkSize = 15 + Math.floor(Math.random() * 10);

        let currentType = 'normal';
        let currentChunk = '';

        for (let i = 0; i < text.length; i++) {
            currentChunk += text[i];

            if (currentChunk.length >= maxChunkSize || /[,.?!;:]/.test(text[i])) {
                chunks.push({
                    content: currentChunk,
                    type: currentType
                });

                const types = ['normal', 'html', 'paste'];
                currentType = types[Math.floor(Math.random() * types.length)];
                currentChunk = '';
            }
        }

        if (currentChunk) {
            chunks.push({
                content: currentChunk,
                type: currentType
            });
        }

        return chunks;
    }

    // 使用原生方法设置值
    function setNativeValue(element, value) {
        const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set;
        const prototype = Object.getPrototypeOf(element);
        const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;

        if (valueSetter && valueSetter !== prototypeValueSetter) {
            prototypeValueSetter.call(element, value);
        } else {
            valueSetter.call(element, value);
        }
    }

    // 模拟粘贴操作
    function simulatePaste(element, text) {
        const clipboardData = new DataTransfer();
        clipboardData.setData('text/plain', text);

        const pasteEvent = new ClipboardEvent('paste', {
            bubbles: true,
            cancelable: true,
            clipboardData: clipboardData,
            composed: true
        });

        element.dispatchEvent(pasteEvent);
    }

    // 触发完整的输入事件序列
    function triggerInputEvents(element) {
        const events = [
            'focus',
            'input',
            'change',
            'keydown',
            'keypress',
            'keyup'
        ];

        events.forEach(eventName => {
            const event = new Event(eventName, { bubbles: true, cancelable: true, composed: true });
            element.dispatchEvent(event);
        });

        if (document.activeElement === element) {
            document.dispatchEvent(new Event('selectionchange'));
        }
    }

    // 模拟真实鼠标点击按钮
    async function realisticClick(element) {
        // 获取元素位置
        const rect = element.getBoundingClientRect();

        // 计算点击位置
        const clickX = rect.left + rect.width / 2 + (Math.random() * 10 - 5);
        const clickY = rect.top + rect.height / 2 + (Math.random() * 10 - 5);

        // 模拟鼠标移动轨迹
        const moveToButton = (x, y) => {
            const moveEvent = new MouseEvent('mousemove', {
                bubbles: true,
                cancelable: true,
                clientX: x,
                clientY: y,
                view: window,
                buttons: 0
            });
            document.dispatchEvent(moveEvent);
        };

        // 贝塞尔曲线移动
        const startX = clickX + (Math.random() * 100 - 50);
        const startY = clickY + (Math.random() * 100 - 50);
        const controlX = (startX + clickX) / 2 + (Math.random() * 40 - 20);
        const controlY = (startY + clickY) / 2 + (Math.random() * 40 - 20);

        const points = [];
        for (let t = 0; t <= 1; t += 0.1) {
            const x = Math.pow(1 - t, 2) * startX + 2 * (1 - t) * t * controlX + Math.pow(t, 2) * clickX;
            const y = Math.pow(1 - t, 2) * startY + 2 * (1 - t) * t * controlY + Math.pow(t, 2) * clickY;
            points.push({ x, y });
        }

        // 移动鼠标
        for (const point of points) {
            moveToButton(point.x, point.y);
            await delay(10 + Math.random() * 15);
        }

        // 鼠标交互序列
        element.dispatchEvent(new MouseEvent('mouseenter', {
            bubbles: true,
            cancelable: true,
            clientX: clickX,
            clientY: clickY,
            view: window,
            buttons: 0
        }));
        await delay(120 + Math.random() * 80);

        element.dispatchEvent(new MouseEvent('mousedown', {
            bubbles: true,
            cancelable: true,
            clientX: clickX,
            clientY: clickY,
            view: window,
            buttons: 1,
            button: 0
        }));
        await delay(70 + Math.random() * 30);

        element.dispatchEvent(new MouseEvent('mouseup', {
            bubbles: true,
            cancelable: true,
            clientX: clickX,
            clientY: clickY,
            view: window,
            buttons: 0,
            button: 0
        }));

        element.dispatchEvent(new MouseEvent('click', {
            bubbles: true,
            cancelable: true,
            clientX: clickX,
            clientY: clickY,
            view: window,
            buttons: 0,
            button: 0
        }));

        element.dispatchEvent(new MouseEvent('mouseleave', {
            bubbles: true,
            cancelable: true,
            clientX: clickX + 10,
            clientY: clickY + 10,
            view: window,
            buttons: 0
        }));

        await delay(150 + Math.random() * 100);
    }

    try {
        // 定位输入框
        const textarea = await waitForElement([
            '[data-testid="chat_input_input"]',
            '.semi-input-textarea.semi-input-textarea-autosize',
            'textarea[placeholder="发消息、输入 @ 选择技能或 / 选择文件"]'
        ]);

        // 准备输入框
        textarea.focus();
        await delay(600 + Math.random() * 200);

        // 清除可能存在的内容
        textarea.value = '';
        triggerInputEvents(textarea);
        await delay(200 + Math.random() * 100);

        // 注入内容
        await injectText(textarea, query);

        // 调整输入框高度
        textarea.style.height = 'auto';
        textarea.style.height = `${textarea.scrollHeight}px`;
        await delay(200 + Math.random() * 100);

        // 验证内容
        if (textarea.value.trim() !== query.trim()) {
            console.warn(`内容验证不匹配(期望: ${query}, 实际: ${textarea.value}),尝试修复`);
            setNativeValue(textarea, query);
            triggerInputEvents(textarea);
            await delay(300 + Math.random() * 150);

            if (textarea.value.trim() !== query.trim()) {
                throw new Error(`内容验证失败:期望 "${query}",实际 "${textarea.value}"`);
            }
        }

        console.log(`内容已成功设置:${query}`);
        await delay(500 + Math.random() * 200);

        // 定位发送按钮
        const sendButton = await waitForElement([
            '[data-testid="chat_input_send_button"]',
            '.send-btn-DDB6yN:not([disabled])',
            '.semi-button-primary:not([disabled])'
        ]);

        // 发送消息
        console.log('准备发送消息...');

        // 确保输入框有焦点
        textarea.focus();
        await delay(200 + Math.random() * 100);

        // 点击发送按钮
        await realisticClick(sendButton);

        // 等待发送结果
        await delay(1000 + Math.random() * 500);

        // 验证是否发送成功
        if (textarea.value.trim() !== '') {
            console.warn('警告:发送后输入框未清空,可能发送失败');

            // 尝试使用Enter键发送
            const enterEvent = new KeyboardEvent('keydown', {
                bubbles: true,
                cancelable: true,
                key: 'Enter',
                code: 'Enter',
                keyCode: 13,
                which: 13,
                location: 0,
                repeat: false,
                isComposing: false,
                view: window
            });
            textarea.dispatchEvent(enterEvent);

            await delay(150);

            const keyupEvent = new KeyboardEvent('keyup', {
                bubbles: true,
                cancelable: true,
                key: 'Enter',
                code: 'Enter',
                keyCode: 13,
                which: 13,
                location: 0,
                repeat: false,
                isComposing: false,
                view: window
            });
            textarea.dispatchEvent(keyupEvent);

            await delay(1000 + Math.random() * 500);

            if (textarea.value.trim() !== '') {
                throw new Error('消息发送失败:输入框内容未清空');
            }
        }

        console.log('消息已成功发送!');

    } catch (error) {
        console.error('脚本执行失败:', error.message);
        console.trace('错误堆栈跟踪');
    }
}

// 执行条件:仅在豆包聊天页面执行
if (window.location.host === 'www.doubao.com' && window.location.pathname.startsWith('/chat')) {
    try {
        addDoubaoQueryParam();
    } catch (e) {
        console.error('脚本主流程异常:', e);
    }
}