// ==UserScript==
// @name 网课搜题助手 - 悬浮窗版
// @namespace http://tampermonkey.net/
// @version 1.1
// @description 免费搜题
// @author Assistant
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_notification
// @connect tk.swk.tw
// ==/UserScript==
(function() {
'use strict';
// 防止在 iframe 或嵌套页面中重复创建
if (window.top !== window.self) {
// 在 iframe 中不运行,避免重复
return;
}
// 防止脚本重复初始化
if (window.__tikuAssistantInitialized) {
return;
}
window.__tikuAssistantInitialized = true;
// ---------- 配置参数 ----------
const API_URL = 'https://tk.swk.tw/api/search.php';
const DEFAULT_KEY = 'sk-ce698b6e3ff5b787da79bc5e5c23c345759776cc9bb1613d';
const TIKU_URL = 'https://tk.swk.tw/';
// 存储Key
let userKey = GM_getValue('tiku_api_key', DEFAULT_KEY);
// ---------- 工具函数:HTML转义 ----------
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// ---------- 全局变量:拖动状态 ----------
let isDragging = false;
let dragStartX = 0, dragStartY = 0;
let floatingLeft = 0, floatingTop = 0;
// ---------- 创建可拖动悬浮窗(一体化) ----------
function createFloatingWindow() {
// 检查是否已存在
if (document.getElementById('tiku-floating-window')) {
return document.getElementById('tiku-floating-window');
}
// 悬浮窗容器
const floatingWin = document.createElement('div');
floatingWin.id = 'tiku-floating-window';
floatingWin.style.cssText = `
position: fixed;
width: 460px;
max-width: 90vw;
background: #ffffff;
border-radius: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(0, 0, 0, 0.05);
font-family: 'Segoe UI', 'Poppins', system-ui, -apple-system, sans-serif;
z-index: 10000;
cursor: default;
overflow: hidden;
backdrop-filter: blur(0);
transition: box-shadow 0.2s;
right: 20px;
top: 80px;
display: none;
`;
// 可拖动头部
const header = document.createElement('div');
header.id = 'tiku-drag-handle';
header.style.cssText = `
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
padding: 14px 20px;
cursor: grab;
user-select: none;
display: flex;
justify-content: space-between;
align-items: center;
color: white;
border-bottom: 1px solid rgba(255,255,255,0.1);
`;
header.innerHTML = `
📚
题库助手
悬浮窗
`;
// 内容区(可折叠)
const contentWrapper = document.createElement('div');
contentWrapper.id = 'tiku-content-wrapper';
contentWrapper.style.cssText = `
transition: all 0.3s ease;
overflow: hidden;
`;
const content = document.createElement('div');
content.style.cssText = `
padding: 20px;
background: #f9fafb;
`;
// 卡密配置区域
const keySection = document.createElement('div');
keySection.style.cssText = `
background: white;
border-radius: 18px;
padding: 14px;
margin-bottom: 20px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
border: 1px solid #e5e7eb;
`;
keySection.innerHTML = `
🔑
API 密钥
可选
`;
// 搜题区域
const searchSection = document.createElement('div');
searchSection.style.cssText = `
background: white;
border-radius: 18px;
padding: 14px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
border: 1px solid #e5e7eb;
`;
searchSection.innerHTML = `
🔍
智能搜题
`;
content.appendChild(keySection);
content.appendChild(searchSection);
contentWrapper.appendChild(content);
floatingWin.appendChild(header);
floatingWin.appendChild(contentWrapper);
document.body.appendChild(floatingWin);
// ----- 拖动逻辑 -----
const dragHandle = document.getElementById('tiku-drag-handle');
dragHandle.addEventListener('mousedown', (e) => {
if (e.target.id === 'tiku-minimize-btn' || e.target.id === 'tiku-close-floating') return;
isDragging = true;
dragStartX = e.clientX;
dragStartY = e.clientY;
const rect = floatingWin.getBoundingClientRect();
floatingLeft = rect.left;
floatingTop = rect.top;
floatingWin.style.cursor = 'grabbing';
dragHandle.style.cursor = 'grabbing';
e.preventDefault();
});
window.addEventListener('mousemove', (e) => {
if (!isDragging) return;
let dx = e.clientX - dragStartX;
let dy = e.clientY - dragStartY;
let newLeft = floatingLeft + dx;
let newTop = floatingTop + dy;
// 边界限制
newLeft = Math.min(Math.max(0, newLeft), window.innerWidth - floatingWin.offsetWidth);
newTop = Math.min(Math.max(0, newTop), window.innerHeight - floatingWin.offsetHeight);
floatingWin.style.left = newLeft + 'px';
floatingWin.style.top = newTop + 'px';
floatingWin.style.right = 'auto';
floatingWin.style.bottom = 'auto';
});
window.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
floatingWin.style.cursor = 'default';
dragHandle.style.cursor = 'grab';
}
});
// 最小化/还原功能
let isMinimized = false;
const minimizeBtn = document.getElementById('tiku-minimize-btn');
minimizeBtn.addEventListener('click', () => {
isMinimized = !isMinimized;
if (isMinimized) {
contentWrapper.style.height = '0px';
contentWrapper.style.padding = '0';
minimizeBtn.innerHTML = '+';
minimizeBtn.style.transform = 'scale(1.1)';
} else {
contentWrapper.style.height = 'auto';
contentWrapper.style.padding = '';
minimizeBtn.innerHTML = '−';
minimizeBtn.style.transform = 'scale(1)';
}
});
// 关闭悬浮窗(隐藏)
const closeBtn = document.getElementById('tiku-close-floating');
closeBtn.addEventListener('click', () => {
floatingWin.style.display = 'none';
// 同时显示悬浮球
const ball = document.getElementById('tiku-float-ball');
if (ball) ball.style.display = 'flex';
});
// 按钮悬停效果
const btns = [minimizeBtn, closeBtn];
btns.forEach(btn => {
btn.addEventListener('mouseenter', () => btn.style.background = 'rgba(255,255,255,0.3)');
btn.addEventListener('mouseleave', () => btn.style.background = 'rgba(255,255,255,0.15)');
});
// 保存密钥
const saveKeyBtn = document.getElementById('tiku-save-key');
const keyInput = document.getElementById('tiku-key-input');
const keyStatus = document.getElementById('key-status-msg');
saveKeyBtn.addEventListener('click', () => {
const newKey = keyInput.value.trim();
if (newKey === '') {
userKey = DEFAULT_KEY;
GM_setValue('tiku_api_key', DEFAULT_KEY);
showKeyStatus('✅ 已恢复默认密钥', '#10b981');
} else {
userKey = newKey;
GM_setValue('tiku_api_key', newKey);
showKeyStatus('✅ 密钥保存成功', '#10b981');
}
setTimeout(() => { keyStatus.style.display = 'none'; }, 2000);
});
function showKeyStatus(msg, color) {
keyStatus.textContent = msg;
keyStatus.style.color = color;
keyStatus.style.display = 'block';
}
// 题库网站按钮
const websiteBtn = document.getElementById('tiku-goto-website');
websiteBtn.addEventListener('click', () => {
window.open(TIKU_URL, '_blank');
});
// 搜题逻辑
const searchBtn = document.getElementById('tiku-search-btn');
const questionInput = document.getElementById('tiku-question-input');
const answerArea = document.getElementById('tiku-answer-area');
function performSearch() {
const question = questionInput.value.trim();
if (!question) {
showAnswer('⚠️ 请输入要查询的题目内容', '#f97316');
return;
}
showAnswer('⏳ 正在搜索中,请稍候...', '#3b82f6', true);
const currentKey = GM_getValue('tiku_api_key', DEFAULT_KEY);
GM_xmlhttpRequest({
method: 'POST',
url: API_URL,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
},
data: `question=${encodeURIComponent(question)}&key=${currentKey}`,
onload: function(resp) {
try {
const json = JSON.parse(resp.responseText);
renderAnswer(json, question);
} catch(e) {
showAnswer('❌ 解析失败: ' + e.message, '#ef4444');
}
},
onerror: () => showAnswer('❌ 网络错误,请检查连接', '#ef4444'),
ontimeout: () => showAnswer('⏰ 请求超时,请重试', '#ef4444')
});
}
function renderAnswer(data, question) {
if (data.code === 1) {
let html = `
✅ 查询成功
📌 题目:
${escapeHtml(question)}
💡 答案:
${escapeHtml(data.data)}
`;
if (data.quota_info) {
html += `
📊 剩余: ${data.quota_info.remaining} | 今日: ${data.quota_info.free_remaining}/${data.quota_info.daily_quota}
`;
}
showAnswer(html, '#10b981');
GM_notification({ text: `答案已找到`, title: '题库助手', timeout: 3000 });
} else {
let errMsg = `❌ 查询失败 (${data.code})
${escapeHtml(data.msg || '未知错误')}`;
if (data.data) errMsg += `
详情: ${escapeHtml(data.data)}`;
showAnswer(errMsg, '#ef4444');
}
}
function showAnswer(content, borderColor, isLoading = false) {
answerArea.style.display = 'block';
answerArea.innerHTML = content;
answerArea.style.borderLeft = `4px solid ${borderColor}`;
answerArea.style.background = isLoading ? '#eff6ff' : '#fffbeb';
}
searchBtn.addEventListener('click', performSearch);
questionInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') performSearch();
});
// 输入框美化
const styleInput = (inputEl) => {
inputEl.addEventListener('focus', () => {
inputEl.style.borderColor = '#6366f1';
inputEl.style.boxShadow = '0 0 0 2px rgba(99,102,241,0.1)';
});
inputEl.addEventListener('blur', () => {
inputEl.style.borderColor = '#d1d5db';
inputEl.style.boxShadow = 'none';
});
};
styleInput(keyInput);
styleInput(questionInput);
// 按钮悬停效果
[saveKeyBtn, websiteBtn, searchBtn].forEach(btn => {
btn.addEventListener('mouseenter', () => btn.style.transform = 'translateY(-1px)');
btn.addEventListener('mouseleave', () => btn.style.transform = 'translateY(0)');
});
// 填充当前密钥显示
if (userKey === DEFAULT_KEY) {
keyInput.value = '';
keyInput.placeholder = '使用默认密钥';
} else {
keyInput.value = userKey;
keyInput.placeholder = '已自定义密钥';
}
return floatingWin;
}
// ---------- 创建悬浮球(唤起悬浮窗)----------
function createFloatBall() {
// 检查是否已存在
if (document.getElementById('tiku-float-ball')) {
return document.getElementById('tiku-float-ball');
}
const ball = document.createElement('div');
ball.id = 'tiku-float-ball';
ball.innerHTML = '📚';
ball.style.cssText = `
position: fixed;
bottom: 30px;
right: 30px;
width: 52px;
height: 52px;
background: linear-gradient(135deg, #1e293b, #0f172a);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
cursor: pointer;
box-shadow: 0 4px 15px rgba(0,0,0,0.25);
z-index: 9999;
transition: all 0.3s ease;
font-family: system-ui;
border: 1px solid rgba(255,255,255,0.2);
`;
ball.addEventListener('mouseenter', () => {
ball.style.transform = 'scale(1.1)';
ball.style.boxShadow = '0 8px 25px rgba(0,0,0,0.3)';
});
ball.addEventListener('mouseleave', () => {
ball.style.transform = 'scale(1)';
});
return ball;
}
// ---------- 清理函数(防止重复创建)----------
function cleanup() {
// 移除可能存在的旧元素
const oldFloating = document.getElementById('tiku-floating-window');
const oldBall = document.getElementById('tiku-float-ball');
if (oldFloating) oldFloating.remove();
if (oldBall) oldBall.remove();
}
// ---------- 初始化 ----------
function init() {
// 清理旧的残留元素
cleanup();
// 创建新元素
const floatingWin = createFloatingWindow();
const floatBall = createFloatBall();
document.body.appendChild(floatBall);
// 确保悬浮窗已添加到body
if (!document.body.contains(floatingWin)) {
document.body.appendChild(floatingWin);
}
// 悬浮球点击事件
floatBall.addEventListener('click', () => {
floatingWin.style.display = 'block';
floatBall.style.display = 'none';
// 刷新密钥显示
const keyInput = document.getElementById('tiku-key-input');
if (keyInput) {
const stored = GM_getValue('tiku_api_key', DEFAULT_KEY);
if (stored === DEFAULT_KEY) {
keyInput.value = '';
keyInput.placeholder = '使用默认密钥';
} else {
keyInput.value = stored;
keyInput.placeholder = '已自定义密钥';
}
}
// 清空答案区域(可选)
const answerArea = document.getElementById('tiku-answer-area');
if (answerArea) answerArea.style.display = 'none';
});
// 监听页面动态变化,防止AJAX嵌套页面重复创建
const observer = new MutationObserver((mutations) => {
// 检查是否有重复的悬浮球被添加
const balls = document.querySelectorAll('#tiku-float-ball');
if (balls.length > 1) {
for (let i = 1; i < balls.length; i++) {
balls[i].remove();
}
}
const windows = document.querySelectorAll('#tiku-floating-window');
if (windows.length > 1) {
for (let i = 1; i < windows.length; i++) {
windows[i].remove();
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
console.log('✅ 搜题助手已启动 | 悬浮窗模式 ');
}
// 等待页面加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}