// ==UserScript==
// @name ChatGPT快捷深度搜索(o4-mini联网并自动发送)- 可拖动版
// @namespace http://tampermonkey.net/
// @version 1.3
// @description 一键切换 o4-mini 模型、开启网络搜索并自动发送消息,按钮可拖动并记忆位置
// @author schweigen
// @match https://chatgpt.com/*
// @license MIT
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// 默认位置设置
const DEFAULT_POSITION = {
top: '30%',
right: '0px'
};
// 从存储中获取位置,如果没有则使用默认值
let buttonPosition = GM_getValue('o4MiniButtonPosition', DEFAULT_POSITION);
// Flag to ensure we only modify one request after button click
let pendingModelSwitch = false;
// Flag to track if we need to click the send button
let needToClickSendButton = false;
// Timeout setting for waiting for the send button to appear
const SEND_BUTTON_WAIT_TIMEOUT = 5000;
// 注册(不可用)油猴菜单命令
GM_registerMenuCommand('重置按钮位置', resetButtonPosition);
// 重置按钮位置函数
function resetButtonPosition() {
buttonPosition = DEFAULT_POSITION;
GM_setValue('o4MiniButtonPosition', buttonPosition);
// 如果按钮已存在,则更新其位置
const button = document.getElementById('o4-mini-button');
if (button) {
button.style.top = buttonPosition.top;
button.style.right = buttonPosition.right;
button.style.left = 'auto';
showNotification('按钮位置已重置');
}
}
// Intercept fetch requests to modify the model
const originalFetch = window.fetch;
window.fetch = async function(url, options) {
// Only modify the next conversation API request after button click
if (pendingModelSwitch && typeof url === 'string' && url.endsWith("/backend-api/conversation") && options?.method === "POST") {
// Parse the request body
let body = JSON.parse(options.body);
console.log('Switching model for this conversation to o4-mini');
// Change the model to o4-mini
body.model = "o4-mini";
// Update the request with modified body
options.body = JSON.stringify(body);
// Reset the flag - we only want to modify one request
pendingModelSwitch = false;
}
// Send the request (modified or original)
return originalFetch(url, options);
};
// Function to wait for and click the send button
function waitForAndClickSendButton() {
if (!needToClickSendButton) return;
let hasClickedSendButton = false;
// Use MutationObserver to watch for the send button to appear and become enabled
const observer = new MutationObserver(function(mutations) {
if (hasClickedSendButton) return; // If already clicked, do nothing
// Try to find the send button with precise selector
const sendButton = document.querySelector('button#composer-submit-button[data-testid="send-button"]');
if (sendButton && !sendButton.disabled) {
console.log("Found send button, preparing to click");
hasClickedSendButton = true; // Mark as clicked
// Create and dispatch a click event
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
// Dispatch the event to the send button
sendButton.dispatchEvent(clickEvent);
console.log("Clicked send button, stopping observer");
// Reset the flag
needToClickSendButton = false;
// Stop observing
observer.disconnect();
}
});
// Start observing the document
observer.observe(document, { childList: true, subtree: true });
// Set a timeout to stop watching after the specified time
setTimeout(function() {
if (!hasClickedSendButton) {
console.log("Reached maximum wait time, stopping observer");
observer.disconnect();
needToClickSendButton = false;
}
}, SEND_BUTTON_WAIT_TIMEOUT);
}
// 拖动功能的实现
function makeDraggable(element) {
let isDragging = false;
let startY = 0;
let startTop = 0;
// 鼠标按下时的处理函数
element.addEventListener('mousedown', function(e) {
// 只有当不是点击事件时才进行拖动(例如按住超过200ms)
const downTime = Date.now();
const onMouseMove = function(moveEvent) {
// 检查是否已经按住足够长的时间
if (!isDragging && Date.now() - downTime > 200) {
isDragging = true;
element.style.cursor = 'move';
startY = moveEvent.clientY;
startTop = parseInt(element.style.top) || 0;
}
if (isDragging) {
// 计算新的位置
const newTop = startTop + (moveEvent.clientY - startY);
// 确保按钮不会超出屏幕
const maxTop = window.innerHeight - element.offsetHeight;
const topPosition = Math.max(0, Math.min(newTop, maxTop));
// 应用新位置
element.style.top = `${topPosition}px`;
// 阻止默认行为和事件冒泡
moveEvent.preventDefault();
moveEvent.stopPropagation();
}
};
const onMouseUp = function() {
if (isDragging) {
// 拖动结束,保存位置
buttonPosition = {
top: element.style.top,
right: element.style.right
};
GM_setValue('o4MiniButtonPosition', buttonPosition);
element.style.cursor = 'pointer';
isDragging = false;
// 显示提示
showNotification('按钮位置已保存');
}
// 移除事件监听器
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
// 添加事件监听器
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
}
// Function to create and add our button
function addO4MiniButton() {
// Check if button already exists
if (document.getElementById('o4-mini-button')) return;
// Create button container
const buttonContainer = document.createElement('div');
buttonContainer.id = 'o4-mini-button';
buttonContainer.style.cssText = `
position: fixed;
top: ${buttonPosition.top};
right: ${buttonPosition.right};
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: linear-gradient(140.91deg, #7367F0 12.61%, #574AB8 76.89%);
color: white;
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
font-weight: bold;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
transition: background 0.3s ease;
font-size: 18px;
user-select: none;
`;
buttonContainer.textContent = '搜';
// 添加拖动功能
makeDraggable(buttonContainer);
// Add click handler
buttonContainer.addEventListener('click', function(e) {
// 阻止因为拖动导致的点击触发
if (e.detail === 0) return;
// Set flag to modify the next conversation request
pendingModelSwitch = true;
// Set flag to click send button after activating search
needToClickSendButton = true;
// 立即开始监听发送按钮 - 不等待搜索按钮的激活
waitForAndClickSendButton();
// 同时处理搜索按钮
const searchButtons = Array.from(document.querySelectorAll('button[aria-label="Search"]'));
if (searchButtons.length > 0) {
// Find the search button that's not already activated
const searchButton = searchButtons.find(btn => btn.getAttribute('aria-pressed') === 'false');
if (searchButton) {
searchButton.click();
showNotification('已激活联网搜索和自动发送');
} else {
showNotification('联网搜索已激活,准备发送');
}
} else {
showNotification('未找到搜索按钮,但会尝试发送');
}
// Visual feedback
this.style.background = 'linear-gradient(140.91deg, #2ecc71 12.61%, #3498db 76.89%)';
// Reset button after 3 seconds
setTimeout(() => {
this.style.background = 'linear-gradient(140.91deg, #7367F0 12.61%, #574AB8 76.89%)';
}, 3000);
});
// Add button to the page
document.body.appendChild(buttonContainer);
}
// Simple notification function
function showNotification(message) {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px 20px;
border-radius: 4px;
z-index: 10001;
transition: opacity 0.3s ease;
`;
notification.textContent = message;
document.body.appendChild(notification);
// Remove notification after 3 seconds
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => notification.remove(), 300);
}, 3000);
}
// Periodically check and add button if it doesn't exist
function checkAndAddButton() {
if (document.body) {
addO4MiniButton();
}
}
// Initial attempt to add button
if (document.readyState === 'complete' || document.readyState === 'interactive') {
checkAndAddButton();
} else {
document.addEventListener('DOMContentLoaded', checkAndAddButton);
}
// Keep checking periodically in case the DOM changes
setInterval(checkAndAddButton, 2000);
})();