自动填充URL中的q参数到豆包聊天框并发送
// ==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);
}
}