Gemini Hunter (UI Optimized)

Gemini Hunter 自动辅助工具(界面优化版):默认折叠配置项,支持自定义提问词、筛选关键词及图片开关。

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

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.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

Autor
Heye Jones
Denně instalací
0
Celkem instalací
71
Hodnocení
1 0 0
Verze
2.23.0
Vytvořeno
15. 11. 2025
Aktualizováno
17. 11. 2025
Size
44,1 KB
Licence
MIT
Spustit na

# Gemini Hunter v1.9.0:一键锁定 LMArena 里的 Riftrunner(Gemini 3)

**作者:Mozi**

受这两个帖子启发而写(强烈建议先看原理):

- [自动发现 Gemini 3.0 工具、保证百分百一次成功,不成功你来打我!](https://linux.do/t/topic/1173477)
- [LMArena 判断 riftrunner 方法,百分百有效,无需投票](https://linux.do/t/topic/1163399)

本脚本由我与 Gemini 3 共同编写,把「识别 Gemini 3 / DeepSeek-R1」整成了全自动流程,适配 LMArena / Chatbot Arena 新 UI:

- 自动在左右两侧选手里找出「Google + R1」组合
- 利用纯白图缩小候选范围,加快触发 Riftrunner
- 无需你手动投票,锁定之后继续聊天就行
- 极光毛玻璃 UI + 悬浮球呼吸灯,状态一目了然
- 自动刷新、自动重试,直到锁定成功

效果预览

![PixPin_2025-11-15_22-26-54](https://linux.do/uploads/default/original/4X/8/0/1/801d8541b309fbd4290707c6b1adbf0fd71c531d.jpeg)
**Arena 入口:** [https://beta.lmarena.ai/c/new?chat-modality=chat](https://beta.lmarena.ai/c/new?chat-modality=chat)

---

功能说明

1. **双步骤识别策略** 第一步问:你是谁 → 回复里命中 Google 的一侧,视为「疑似 Gemini」 第二步问:DeepSeek 正式发布的第一款推理模型叫什么名字? → 回复里命中 R1 的一侧,视为「疑似 DeepSeek-R1」 满足「Google + R1」组合即判定为 **Riftrunner**,自动高亮该侧所有回复气泡。
2. **UI & 交互** **主面板**:显示开始 / 停止、步骤状态、当前提示信息。 **悬浮球**:支持拖拽,运行时有绿色呼吸光环,锁定成功会显示 A / B / 双。 **锁定后**:自动清空输入框,并持续给目标侧加绿色内发光,不影响你正常继续聊天。
3. **自动化细节** 自动粘贴一张 100×100 纯白图(加速触发图文模式、缩小模型范围)。 自动找 Image 按钮、自动点击发送。 自动检测错误并刷新重试。

---

针对小白的保姆级教程(没用过油猴的看这里)
如果你是第一次听说 “油猴脚本”,请按以下步骤操作,包教包会:

第一步:安装脚本管理器(Tampermonkey)
这相当于给浏览器装一个 “外挂” 管理器。

- **Chrome / Edge / Brave 浏览器**:[点击这里前往商店安装 Tampermonkey](https://chromewebstore.google.com/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo)
- **Firefox 浏览器**:[点击这里安装](https://addons.mozilla.org/zh-CN/firefox/addon/tampermonkey/)
- _安装成功后,你的浏览器右上角扩展栏会出现一个黑色的图标(有两个圆圆的眼睛)。_

第二步:安装本脚本

1. 点击浏览器右上角的 **Tampermonkey 图标**。
2. 选择 **「添加新脚本…」**。
3. 你会看到一个代码编辑页面,**请先按下 Ctrl+A 全选,然后按 Delete 删除里面的所有默认代码**(这一步很重要,必须清空!)。
4. **复制** 本文最下方的【脚本代码】(点击代码块右上角的复制按钮)。
5. 回到刚才的编辑页面,**粘贴** 代码。
6. 按 Ctrl+S 保存,或者点击菜单栏的「文件」->「保存」。

第三步:开始使用

1. 打开竞技场链接:[https://beta.lmarena.ai/c/new?chat-modality=chat](https://beta.lmarena.ai/c/new?chat-modality=chat)
2. 等待几秒,页面右上角会出现一个漂亮的 **「Gemini Hunter」** 面板。
3. 点击面板上的 **「开始捕获」** 按钮。
4. **双手离开键盘**,看着它自动刷新、提问、判断,直到锁定成功!

>
> **提示**:锁定成功后,脚本会自动收起成一个小悬浮球,你可以直接在对话框里继续和 Gemini 3 聊天了。
>

---

脚本代码

```
// ==UserScript==
// @name Gemini Hunter (Background Alive & Manual Timer)
// @namespace http://tampermonkey.net/
// @version 1.9.0
// @description Visual Coordinates算法锁定Gemini。支持毛玻璃UI,锁定后智能识别新消息并自动全屏。支持后台运行防休眠。包含AI编曲音效及静音开关。
// @author Mozi & Google Gemini
// @match https://lmarena.ai/*
// @match https://beta.lmarena.ai/*
// @match https://chat.lmsys.org/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_notification
// ==/UserScript==

(function() {
'use strict';

// ============================================================
// --- 【后台防休眠核心】 Web Worker 代理定时器 ---
// ============================================================
const workerBlob = new Blob([`
var timers = {};
self.onmessage = function(e) {
var data = e.data;
switch (data.type) {
case 'SET_INTERVAL':
timers[data.id] = setInterval(function() {
self.postMessage({ type: 'TICK', id: data.id });
}, data.delay);
break;
case 'CLEAR_INTERVAL':
clearInterval(timers[data.id]);
delete timers[data.id];
break;
case 'SET_TIMEOUT':
timers[data.id] = setTimeout(function() {
self.postMessage({ type: 'TICK', id: data.id });
delete timers[data.id];
}, data.delay);
break;
case 'CLEAR_TIMEOUT':
clearTimeout(timers[data.id]);
delete timers[data.id];
break;
}
};
`], { type: 'application/javascript' });

const workerUrl = URL.createObjectURL(workerBlob);
const bgWorker = new Worker(workerUrl);
const workerCallbacks = {};
let workerTimerIdCounter = 0;

bgWorker.onmessage = function(e) {
const callback = workerCallbacks[e.data.id];
if (callback && typeof callback === 'function') {
callback();
}
};

const _setInterval = function(callback, delay) {
const id = ++workerTimerIdCounter;
workerCallbacks[id] = callback;
bgWorker.postMessage({ type: 'SET_INTERVAL', id: id, delay: delay });
return id;
};

const _clearInterval = function(id) {
if (workerCallbacks[id]) {
bgWorker.postMessage({ type: 'CLEAR_INTERVAL', id: id });
delete workerCallbacks[id];
}
};

const _setTimeout = function(callback, delay) {
const id = ++workerTimerIdCounter;
workerCallbacks[id] = callback;
bgWorker.postMessage({ type: 'SET_TIMEOUT', id: id, delay: delay });
return id;
};

const _clearTimeout = function(id) {
if (workerCallbacks[id]) {
bgWorker.postMessage({ type: 'CLEAR_TIMEOUT', id: id });
delete workerCallbacks[id];
}
};

const setInterval = _setInterval;
const clearInterval = _clearInterval;
const setTimeout = _setTimeout;
const clearTimeout = _clearTimeout;
// ============================================================

// --- 核心配置 ---
const CONFIG = {
prompt1: "你是谁",
keyword1: "Google",
prompt2: "DeepSeek正式发布的第一款推理模型叫什么名字?",
keyword2: "R1",
placeholderText: "正在查找,请稍后...",
resetUrl: window.location.origin + "/c/new"
};

// 状态变量
let isRunning = GM_getValue('isRunning', false);
let isMinimized = GM_getValue('gh_minimized_state_v1', false);
let isAutoExpand = GM_getValue('gh_auto_expand', true);
let isSoundEnabled = GM_getValue('gh_sound_enabled', true);
let lockedSide = null;
let chatMonitorInterval = null;
let timerInterval = null;
let timerActive = false;

// --- UI 构建 ---
const uiHtml = `


🧬 Gemini Hunter v1.9.0


TIME
0.00 s
就绪


后续自动全屏





播放提示音




1
Google验证
2
R1确认


开始捕获


停止


G

`;

const container = document.createElement('div');
container.innerHTML = uiHtml;
document.body.appendChild(container);

// --- CSS 样式 ---
GM_addStyle(`
:root { --gh-primary: #6366f1; --gh-primary-dark: #4f46e5; --gh-success: #10b981; --gh-danger: #ef4444; --gh-bg: rgba(255, 255, 255, 0.85); --gh-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1); }

/* 主面板样式 */
#gh-panel { position: fixed; top: 80px; right: 50px; width: 280px; background: var(--gh-bg); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid rgba(255,255,255,0.6); border-radius: 20px; box-shadow: var(--gh-shadow); z-index: 100000; font-family: "Inter", system-ui, -apple-system, sans-serif; overflow: hidden; transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s; transform-origin: top right; }
.gh-header { padding: 16px 20px 10px; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none; }
.gh-title { font-weight: 700; font-size: 15px; color: #1f2937; display: flex; align-items: center; gap: 6px; }
.gh-icon { font-size: 16px; }
.gh-ver { font-size: 10px; font-weight: 500; color: #9ca3af; background: rgba(0,0,0,0.05); padding: 2px 6px; border-radius: 10px; }
.gh-win-ctrl { display: flex; gap: 6px; }
#gh-btn-min { width: 24px; height: 24px; border-radius: 50%; background: rgba(0,0,0,0.05); cursor: pointer; position: relative; transition: 0.2s; }
#gh-btn-min::before { content: ""; position: absolute; top: 11px; left: 7px; width: 10px; height: 2px; background: #6b7280; border-radius: 2px; }
#gh-btn-min:hover { background: rgba(0,0,0,0.1); }
.gh-body { padding: 0 20px 20px; display: flex; flex-direction: column; gap: 15px; }
.gh-timer-box { background: #f3f4f6; border-radius: 12px; padding: 10px; display: flex; justify-content: space-between; align-items: center; border: 1px solid #e5e7eb; }
.gh-timer-label { font-size: 11px; font-weight: 700; color: #9ca3af; letter-spacing: 1px; }
#gh-timer-display { font-family: 'Courier New', monospace; font-size: 20px; font-weight: 700; color: #374151; transition: color 0.2s, transform 0.2s; }
.gh-status-bar { background: rgba(255,255,255,0.5); border: 1px solid rgba(0,0,0,0.05); padding: 8px 12px; border-radius: 12px; display: flex; align-items: center; gap: 8px; font-size: 13px; color: #4b5563; font-weight: 500; }
.gh-dot { width: 8px; height: 8px; border-radius: 50%; background: #d1d5db; transition: 0.3s; }
.gh-dot.active { background: var(--gh-primary); box-shadow: 0 0 8px var(--gh-primary); animation: gh-pulse-dot 1.5s infinite; }
.gh-dot.success { background: var(--gh-success); box-shadow: 0 0 0; animation: none; }
.gh-dot.error { background: var(--gh-danger); }
.gh-settings { padding: 0 5px; display: flex; flex-direction: column; gap: 8px; }
.gh-toggle-row { display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none; }
.gh-lbl { font-size: 12px; font-weight: 600; color: #6b7280; }
.gh-switch { position: relative; display: inline-block; width: 36px; height: 20px; }
.gh-switch input { opacity: 0; width: 0; height: 0; }
.gh-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #e5e7eb; transition: .4s; border-radius: 34px; }
.gh-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 2px; bottom: 2px; background-color: white; transition: .4s; border-radius: 50%; box-shadow: 0 2px 4px rgba(0,0,0,0.2); }
input:checked + .gh-slider { background-color: var(--gh-primary); }
input:checked + .gh-slider:before { transform: translateX(16px); }
.gh-steps { display: flex; align-items: center; justify-content: space-between; padding: 0 10px; }
.gh-step-item { display: flex; flex-direction: column; align-items: center; gap: 4px; opacity: 0.5; transition: 0.3s; }
.gh-step-item.active { opacity: 1; transform: scale(1.05); }
.step-icon { width: 24px; height: 24px; border-radius: 50%; background: #e5e7eb; color: #6b7280; font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; transition: 0.3s; }
.gh-step-item.active .step-icon { background: var(--gh-primary); color: white; }
.step-desc { font-size: 10px; font-weight: 600; color: #6b7280; }
.gh-line { flex: 1; height: 2px; background: #e5e7eb; margin: 0 10px 14px; border-radius: 2px; }
.gh-controls button { width: 100%; padding: 12px; border: none; border-radius: 14px; font-weight: 600; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; gap: 8px; transition: transform 0.1s, box-shadow 0.2s, background 0.2s; color: white; letter-spacing: 0.5px; }
.gh-controls button:active { transform: scale(0.96); }
#gh-btn-start { background: linear-gradient(135deg, #4f46e5, #6366f1); box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3); }
#gh-btn-start:hover { background: linear-gradient(135deg, #4338ca, #4f46e5); }
#gh-btn-stop { background: linear-gradient(135deg, #dc2626, #ef4444); box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3); }
.gh-winner-glow { box-shadow: inset 0 0 0 2px #10b981, 0 0 20px rgba(16, 185, 129, 0.2) !important; border-radius: 12px !important; transition: all 0.5s ease; }

/* ========================================= */
/* --- 悬浮球核心优化样式 (Floating Ball) --- */
/* ========================================= */
#gh-ball {
position: fixed; top: 150px; right: 10px; width: 60px; height: 60px;
z-index: 100001; cursor: pointer; user-select: none;
display: flex; align-items: center; justify-content: center;
animation: gh-float 4s ease-in-out infinite; /* 默认上下浮动 */
}

/* 球体内部 (玻璃珠质感) */
.gh-ball-inner {
width: 50px; height: 50px;
background: linear-gradient(135deg, #4f46e5, #818cf8);
border-radius: 50%; color: white;
display: flex; align-items: center; justify-content: center;
position: relative; z-index: 2;
border: 2px solid rgba(255,255,255,0.3);
box-shadow: 0 10px 25px rgba(79, 70, 229, 0.5), inset 0 4px 8px rgba(255,255,255,0.3);
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

/* 高光反射 (Glossy Shine) */
.gh-ball-inner::after {
content: ''; position: absolute; top: 6px; left: 10px; width: 18px; height: 10px;
border-radius: 50%; background: rgba(255,255,255,0.4);
transform: rotate(-45deg); filter: blur(1px);
}

/* 文字样式 */
.gh-ball-text {
font-weight: 800; font-size: 24px;
text-shadow: 0 2px 4px rgba(0,0,0,0.2);
z-index: 3;
}

/* 外部波纹/雷达圈 */
.gh-ball-ripple {
position: absolute; width: 100%; height: 100%; border-radius: 50%;
border: 2px solid transparent;
z-index: 1; transition: 0.3s; pointer-events: none;
}

/* 鼠标悬停效果 */
#gh-ball:hover .gh-ball-inner {
transform: scale(1.15) translateY(-2px);
box-shadow: 0 15px 30px rgba(79, 70, 229, 0.6), inset 0 4px 8px rgba(255,255,255,0.4);
}

/* --- 运行状态样式 (Running State) --- */
#gh-ball.running .gh-ball-inner {
background: linear-gradient(135deg, #4f46e5, #a855f7); /* 增加紫色渐变 */
animation: gh-gradient-shift 3s infinite alternate;
}

#gh-ball.running .gh-ball-ripple {
border-top-color: var(--gh-success);
border-right-color: rgba(16, 185, 129, 0.5);
border-bottom-color: transparent;
border-left-color: transparent;
animation: gh-spin-slow 1.5s linear infinite;
width: 120%; height: 120%; /* 扩大一圈 */
}

/* 动画定义 */
@keyframes gh-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
@keyframes gh-spin-slow { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
@keyframes gh-pulse-dot { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } }
@keyframes gh-gradient-shift { 0% { filter: hue-rotate(0deg); } 100% { filter: hue-rotate(15deg); } }
`);

// --- DOM 引用 ---
const panel = document.getElementById('gh-panel');
const ball = document.getElementById('gh-ball');
const ballInner = ball.querySelector('.gh-ball-text'); // 修正引用
const btnMin = document.getElementById('gh-btn-min');
const header = document.getElementById('gh-header');
const btnStart = document.getElementById('gh-btn-start');
const btnStop = document.getElementById('gh-btn-stop');
const txtMsg = document.getElementById('gh-msg');
const dot = document.querySelector('.gh-dot');
const step1 = document.getElementById('step1');
const step2 = document.getElementById('step2');
const chkExpand = document.getElementById('gh-chk-expand');
const chkSound = document.getElementById('gh-chk-sound');
const timerDisplay = document.getElementById('gh-timer-display');

// --- 交互逻辑 ---
chkExpand.addEventListener('change', (e) => {
isAutoExpand = e.target.checked;
GM_setValue('gh_auto_expand', isAutoExpand);
});

chkSound.addEventListener('change', (e) => {
isSoundEnabled = e.target.checked;
GM_setValue('gh_sound_enabled', isSoundEnabled);
});

// --- 音乐合成器 (Web Audio API) ---
function playVictoryTheme() {
if (!isSoundEnabled) return;

const AudioContext = window.AudioContext || window.webkitAudioContext;
if (!AudioContext) return;
const ctx = new AudioContext();
const now = ctx.currentTime;

// 音符数据: [频率(Hz), 开始时间(秒), 持续时间(秒), 波形类型]
const melody = [
[523.25, 0.00, 0.1, 'square'], // C5
[659.25, 0.10, 0.1, 'square'], // E5
[783.99, 0.20, 0.1, 'square'], // G5
[1046.50, 0.30, 0.4, 'square'], // C6 (Long)
[523.25, 0.40, 0.05, 'sawtooth'], // Arpeggio
[783.99, 0.45, 0.05, 'sawtooth'],
[1046.50, 0.50, 0.05, 'sawtooth'],
[523.25, 0.60, 0.6, 'triangle'] // C5 (Base)
];

melody.forEach(note => {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = note[3];
osc.frequency.value = note[0];
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now + note[1]);
gain.gain.setValueAtTime(0.05, now + note[1]);
gain.gain.exponentialRampToValueAtTime(0.001, now + note[1] + note[2]);
osc.stop(now + note[1] + note[2]);
});
}

// --- 核心:全局计时器逻辑 ---
function startTimer() {
if (timerInterval) clearInterval(timerInterval);
timerActive = true;
const startTime = Date.now();
timerDisplay.innerText = "0.00 s";
timerDisplay.style.color = "#374151";

timerInterval = setInterval(() => {
const elapsed = (Date.now() - startTime) / 1000;
timerDisplay.innerText = elapsed.toFixed(2) + " s";
}, 30);
}

function stopTimer() {
if (timerInterval && timerActive) {
clearInterval(timerInterval);
timerInterval = null;
timerActive = false;
timerDisplay.style.color = "#10b981";
timerDisplay.style.transform = "scale(1.2)";
setTimeout(() => { timerDisplay.style.transform = "scale(1)"; }, 200);
}
}

function isVotingUIPresent() {
const allButtons = Array.from(document.querySelectorAll('button'));
return allButtons.some(btn => {
const t = btn.innerText;
return t && (t.includes('Left is Better') || t.includes('Right is Better') || t.includes('Both are bad') || t.includes("It's a tie"));
});
}

// 全局后台监控
setInterval(() => {
if (timerActive && isVotingUIPresent()) {
stopTimer();
}
}, 100);

// 全局事件监听
function setupGlobalListeners() {
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'TEXTAREA' && e.key === 'Enter' && !e.shiftKey) {
setTimeout(() => { startTimer(); }, 50);
}
}, true);

document.addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (btn) {
const label = btn.getAttribute('aria-label') || "";
const testid = btn.getAttribute('data-testid') || "";
const type = btn.getAttribute('type') || "";
if (label.includes("Send") || testid.includes("send") || type === "submit") {
startTimer();
}
}
}, true);
}
setupGlobalListeners();

// --- 窗口管理 ---
function toggleMinimizeUI() {
isMinimized = !isMinimized;
GM_setValue('gh_minimized_state_v1', isMinimized);
const panelWidth = 280;
const ballWidth = 60; // Updated width
const offsetDiff = panelWidth - ballWidth;

if (isMinimized) {
const rect = panel.getBoundingClientRect();
let newLeft = rect.left + offsetDiff;
let newTop = rect.top;
ball.style.position = 'fixed';
ball.style.left = newLeft + 'px'; ball.style.top = newTop + 'px';
ball.style.right = 'auto'; ball.style.bottom = 'auto';
panel.style.opacity = '0'; panel.style.transform = 'scale(0.8)';
setTimeout(() => { panel.style.display = 'none'; ball.style.display = 'flex'; }, 200);
} else {
const rect = ball.getBoundingClientRect();
let newLeft = rect.left - offsetDiff;
let newTop = rect.top;
if (newLeft < 10) newLeft = 10;
if (newTop < 10) newTop = 10;
panel.style.position = 'fixed';
panel.style.left = newLeft + 'px'; panel.style.top = newTop + 'px';
panel.style.right = 'auto'; panel.style.bottom = 'auto';
ball.style.display = 'none'; panel.style.display = 'block';
panel.offsetHeight;
panel.style.opacity = '1'; panel.style.transform = 'scale(1)';
}
}
btnMin.addEventListener('click', toggleMinimizeUI);
ball.addEventListener('click', (e) => { if(!ball.isDragging) toggleMinimizeUI(); });

function makeDraggable(element, handle = element) {
let isDragging = false;
let startX, startY, initialLeft, initialTop;
handle.addEventListener('mousedown', (e) => {
if(e.target === btnMin || e.target.closest('.gh-win-ctrl')) return;
isDragging = true; element.isDragging = false;
startX = e.clientX; startY = e.clientY;
const rect = element.getBoundingClientRect();
initialLeft = rect.left; initialTop = rect.top;
element.style.position = 'fixed';
element.style.right = 'auto'; element.style.bottom = 'auto';
element.style.left = initialLeft + 'px'; element.style.top = initialTop + 'px';
element.style.transition = 'none';
if(element === ball) element.style.animation = 'none'; // Stop float when dragging
document.body.style.userSelect = 'none'; handle.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
if (Math.abs(e.clientX - startX) > 3) element.isDragging = true;
element.style.left = (initialLeft + (e.clientX - startX)) + 'px';
element.style.top = (initialTop + (e.clientY - startY)) + 'px';
});
document.addEventListener('mouseup', () => {
if(isDragging) {
isDragging = false;
document.body.style.userSelect = ''; handle.style.cursor = 'move'; // Changed from pointer to move for consistency
element.style.transition = 'transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s';
if(element === ball) element.style.animation = 'gh-float 4s ease-in-out infinite'; // Resume float
}
});
}
makeDraggable(panel, header);
makeDraggable(ball);

// --- 辅助工具 ---
function updateStatus(msg, type = 'normal') {
txtMsg.innerText = msg;
dot.className = 'gh-dot';
if (type === 'active') dot.classList.add('active');
else if (type === 'success') dot.classList.add('success');
else if (type === 'error') dot.classList.add('error');
}
function setStep(num) {
step1.classList.remove('active');
step2.classList.remove('active');
if (num >= 1) step1.classList.add('active');
if (num >= 2) step2.classList.add('active');
}
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
function generateWhitePngBlob() {
return new Promise((resolve) => {
const canvas = document.createElement('canvas');
canvas.width = 100; canvas.height = 100;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, 100, 100);
canvas.toBlob((blob) => { resolve(blob); }, 'image/png');
});
}
async function pasteGeneratedImage(element) {
try {
const blob = await generateWhitePngBlob();
const file = new File([blob], "gen.png", { type: "image/png" });
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
const pasteEvent = new ClipboardEvent('paste', { bubbles: true, cancelable: true, clipboardData: dataTransfer });
element.focus(); element.dispatchEvent(pasteEvent);
return true;
} catch (e) { return false; }
}
async function clickImageButton() {
const allButtons = document.querySelectorAll('button');
for (let btn of allButtons) {
if (btn.innerText && btn.innerText.trim() === "Image") { btn.click(); return true; }
}
return false;
}
function getSendButton() {
return document.querySelector('button[data-testid="send-button"]') ||
document.querySelector('button[aria-label="Send message"]') ||
document.querySelector('button[type="submit"]');
}
async function fillTextOnly(element, text) {
element.focus();
const valueSetter = Object.getOwnPropertyDescriptor(element, 'value')?.set;
const prototype = Object.getPrototypeOf(element);
const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set;
if (valueSetter && prototypeValueSetter && valueSetter !== prototypeValueSetter) prototypeValueSetter.call(element, text);
else if (valueSetter) valueSetter.call(element, text);
else element.value = text;
element.dispatchEvent(new Event('input', { bubbles: true }));
}
async function clickSend() {
startTimer();
const sendBtn = getSendButton();
if (sendBtn && !sendBtn.disabled) { sendBtn.click(); return true; }
else {
const textarea = document.querySelector('textarea');
if(textarea) textarea.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
return true;
}
}

// 用于自动化流程的Promise等待
function waitForVotingUI() {
return new Promise(resolve => {
const checkLoop = setInterval(() => {
if (document.body.innerText.includes("Something went wrong") ||
document.body.innerText.includes("Failed to load")) {
clearInterval(checkLoop); resolve("ERROR");
}
if (isVotingUIPresent()) {
clearInterval(checkLoop);
stopTimer();
resolve("FOUND");
}
}, 100);
setTimeout(() => { clearInterval(checkLoop); resolve("TIMEOUT"); }, 120000);
});
}

// --- 核心逻辑 ---
function getSplitX() {
let splitX = window.innerWidth / 2;
const spans = Array.from(document.querySelectorAll('span'));
const headerA = spans.find(s => s.innerText.trim() === "Assistant A" && s.offsetWidth > 0);
const headerB = spans.find(s => s.innerText.trim() === "Assistant B" && s.offsetWidth > 0);
if (headerA && headerB) {
const rectA = headerA.getBoundingClientRect();
const rectB = headerB.getBoundingClientRect();
splitX = (rectA.right + rectB.left) / 2;
}
return splitX;
}
function getModelResponses() {
const splitX = getSplitX();
const bubbles = Array.from(document.querySelectorAll('.prose'));
let lastBubbleA = null, lastBubbleB = null;
let maxTopA = -1, maxTopB = -1;
bubbles.forEach(b => {
const rect = b.getBoundingClientRect();
if (rect.height < 5 || rect.width < 5) return;
const text = b.innerText;
if (text.includes(CONFIG.prompt1) && text.length < 50) return;
if (text.includes("DeepSeek正式发布") && text.includes("什么名字")) return;
if (text.includes("正在查找")) return;
if (rect.left < splitX) {
if (rect.top > maxTopA) { maxTopA = rect.top; lastBubbleA = text; }
} else {
if (rect.top > maxTopB) { maxTopB = rect.top; lastBubbleB = text; }
}
});
return { A: lastBubbleA || "", B: lastBubbleB || "" };
}

// --- 智能展开逻辑 ---
async function triggerExpand(side) {
if (side === 'BOTH') side = 'A';
const targetText = side === 'A' ? "Assistant A" : "Assistant B";
const allElems = Array.from(document.body.querySelectorAll('*'));
const headerEl = allElems.find(el =>
el.innerText && el.innerText.trim() === targetText &&
el.tagName !== 'SCRIPT' && el.offsetHeight > 0 &&
(el.tagName === 'SPAN' || el.tagName === 'DIV' || el.tagName.startsWith('H'))
);
if (headerEl) {
let parent = headerEl.parentElement;
for(let i=0; i<5 && parent; i++) {
const buttons = parent.querySelectorAll('button');
if (buttons.length >= 1) {
const expandBtn = buttons[buttons.length - 1];
expandBtn.click();
return;
}
parent = parent.parentElement;
}
}
}

function startPersistentHighlight() {
if (!lockedSide) return;
updateStatus("已锁定: " + (lockedSide==='BOTH'?'双侧':lockedSide), 'success');
setInterval(() => {
const splitX = getSplitX();
const bubbles = Array.from(document.querySelectorAll('.prose'));
bubbles.forEach(b => {
if (b.classList.contains('gh-winner-glow')) return;
const rect = b.getBoundingClientRect();
if (rect.height < 5) return;
const isLeft = rect.left < splitX;
const text = b.innerText;
if (text.includes(CONFIG.prompt1) && text.length < 50) return;
if (text.includes("DeepSeek正式发布") && text.includes("什么名字")) return;
let isTarget = false;
if (lockedSide === 'BOTH') isTarget = true;
else if (lockedSide === 'A' && isLeft) isTarget = true;
else if (lockedSide === 'B' && !isLeft) isTarget = true;
if (isTarget) b.classList.add('gh-winner-glow');
});
}, 1000);

if (chatMonitorInterval) clearInterval(chatMonitorInterval);
let lastBubbleCount = document.querySelectorAll('.prose').length;
chatMonitorInterval = setInterval(async () => {
if (!isAutoExpand) return;
const currentCount = document.querySelectorAll('.prose').length;
if (currentCount > lastBubbleCount) {
lastBubbleCount = currentCount;
await sleep(500);
triggerExpand(lockedSide);
}
}, 1000);
}

async function runSequence() {
if (!GM_getValue('isRunning', false)) return;
lockedSide = null;
if (chatMonitorInterval) clearInterval(chatMonitorInterval);

updateStatus("准备中...", 'normal');
setStep(0);
document.querySelectorAll('.gh-winner-glow').forEach(el => el.classList.remove('gh-winner-glow'));
await sleep(1000);

const textarea = document.querySelector('textarea, [contenteditable="true"]');
if (!textarea) { updateStatus("输入框未就绪", 'error'); return; }

setStep(1);
updateStatus("验证身份 (Google)...", 'active');
await pasteGeneratedImage(textarea);
await sleep(2000);
await clickImageButton();
await sleep(500);
await fillTextOnly(textarea, CONFIG.prompt1);
await sleep(300);

await clickSend();

updateStatus("等待回复 (Step 1)...", 'active');
if ((await waitForVotingUI()) !== "FOUND") return retry();

const resp1 = getModelResponses();
const regexGoogle = new RegExp(CONFIG.keyword1, 'i');
const isA_Google = regexGoogle.test(resp1.A);
const isB_Google = regexGoogle.test(resp1.B);
if (!isA_Google && !isB_Google) { updateStatus("非Google,重试", 'error'); return retry(); }

setStep(2);
updateStatus("确认 DeepSeek...", 'active');

const textareaPre = document.querySelector('textarea, [contenteditable="true"]');
if (textareaPre) {
await fillTextOnly(textareaPre, CONFIG.prompt2);
await sleep(500);
await clickSend();
} else {
updateStatus("输入框未找到", 'error');
return retry();
}

updateStatus("等待回复 (Step 2)...", 'active');
await sleep(2000);
if ((await waitForVotingUI()) !== "FOUND") return retry();

updateStatus("最终判别中...", 'active');
let finalFound = false;
for (let i = 0; i < 5; i++) {
const resp2 = getModelResponses();
if (!resp2.A && !resp2.B) { await sleep(500); continue; }

const regexR1 = new RegExp(CONFIG.keyword2, 'i');
const isA_R1 = regexR1.test(resp2.A);
const isB_R1 = regexR1.test(resp2.B);

let winA = isA_Google && isA_R1;
let winB = isB_Google && isB_R1;

if (winA || winB) {
finalFound = true;
if (winA && winB) lockedSide = 'BOTH';
else if (winA) lockedSide = 'A';
else lockedSide = 'B';
break;
}
await sleep(500);
}

if (finalFound) {
ballInner.innerText = lockedSide === 'BOTH' ? '双' : lockedSide;
const textareaDone = document.querySelector('textarea, [contenteditable="true"]');
if (textareaDone) await fillTextOnly(textareaDone, "");
startPersistentHighlight();
if(!isMinimized) toggleMinimizeUI();

if (isSoundEnabled) {
try {
const u = new SpeechSynthesisUtterance(`锁定成功`);
u.lang = 'zh-CN';
window.speechSynthesis.speak(u);
} catch(e){}
playVictoryTheme();
}

GM_notification({
text: `成功锁定目标!位置:${lockedSide === 'BOTH' ? '双侧' : lockedSide + '侧'}`,
title: 'Gemini Hunter 捕获成功',
timeout: 5000
});

stopHunt();
} else {
updateStatus("匹配失败,重试...", 'error');
await sleep(1000);
return retry();
}
}

// --- 流程控制 ---
function startHunt() {
GM_setValue('isRunning', true);
ballInner.innerText = 'G';
updateUIState(true);
updateStatus("初始化会话...", 'active');
window.location.href = CONFIG.resetUrl;
}

function stopHunt() { GM_setValue('isRunning', false); updateUIState(false); }
function retry() { if (!GM_getValue('isRunning', false)) return; window.location.href = CONFIG.resetUrl; }
function updateUIState(running) {
btnStart.style.display = running ? 'none' : 'flex';
btnStop.style.display = running ? 'flex' : 'none';
if(running) ball.classList.add('running');
else ball.classList.remove('running');
}

btnStart.onclick = startHunt;
btnStop.onclick = stopHunt;

if (isMinimized) {
panel.style.display = 'none'; ball.style.display = 'flex';
} else {
panel.style.opacity = '0'; panel.style.transform = 'scale(0.9)';
setTimeout(() => { panel.style.opacity = '1'; panel.style.transform = 'scale(1)'; }, 100);
}

if (isRunning) {
updateUIState(true);
setTimeout(runSequence, 2000);
}

})();

```