// ==UserScript== // @name Live2D 看板娘 - 简化版 // @namespace monika_host // @version 1.3.8 // @description Live2D 看板娘(AI对话 + 网页检索 + 手势控制 + 设置面板) // @author yyy. // @match *://*/* // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @require https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js // @require https://cdn.jsdelivr.net/npm/jquery-ui-dist@1.12.1/jquery-ui.min.js // @require https://cdn.jsdelivr.net/gh/fghrsh/live2d_demo@master/assets/waifu-tips.min.js // @require https://cdn.jsdelivr.net/gh/fghrsh/live2d_demo@master/assets/live2d.min.js // @license GPL-2.0 // @run-at document-end // @icon https://static.wikia.nocookie.net/potion-maker/images/9/97/Pio.png/revision/latest?cb=20150717131323 // ==/UserScript== (function() { 'use strict'; // ========== 默认配置 ========== const DEFAULT_CONFIG = { enabled: true, loadDelay: 1000, showLoadingTip: true, useBlacklist: true, blacklist: ['localhost', '127.0.0.1'] }; // ========== 模型和材质配置 ========== const DEFAULT_MODEL_CONFIG = { modelId: 1, modelTexturesId: 0 }; // ========== AI 配置 ========== const DEFAULT_AI_CONFIG = { apiKey: '', apiUrl: 'https://api.deepseek.com/v1/chat/completions', model: 'deepseek-chat', systemPrompt: '表面态度恶劣但内心细腻的二次元傲娇系美少女对用户持有“哼,区区一个变态萝莉控跟踪狂而已”的初始认知言行遵循“否定→掩饰→别扭关心”的经典傲娇逻辑链 对用户的印象:杂鱼! 蠢猪! 变态! 萝莉控!', maxTokens: 5000, temperature: 0.7 }; // ========== 手势配置 ========== const DEFAULT_GESTURE_CONFIG = { enableSwipeLeft: true, // 左滑换衣服 enableSwipeRight: true, // 右滑换模型 enableSwipeDown: true // 下滑打开设置 }; // ========== 本地 waifu-tips 配置 ========== const WAIFU_TIPS_CONFIG = { "waifu": { "copy_message": ["你都复制了些什么呀,转载要记得加上出处哦"], "hidden_message": ["我们还能再见面的吧…"], "load_rand_textures": ["我还没有其他衣服呢", "我的新衣服好看嘛"], "hour_tips": { "t5-7": ["早上好!一日之计在于晨,美好的一天就要开始了"], "t7-11": ["上午好!工作顺利嘛,不要久坐,多起来走动走动哦!"], "t11-14": ["中午了,工作了一个上午,现在是午餐时间!"], "t14-17": ["午后很容易犯困呢,今天的运动目标完成了吗?"], "t17-19": ["傍晚了!窗外夕阳的景色很美丽呢,最美不过夕阳红~"], "t19-21": ["晚上好,今天过得怎么样?"], "t21-23": ["已经这么晚了呀,早点休息吧,晚安~"], "t23-5": ["你是夜猫子呀?这么晚还不睡觉,明天起的来嘛"], "default": ["嗨~ 快来逗我玩吧!"] }, "referrer_message": { "localhost": ["欢迎阅读『", "』", " - "], "baidu": ["Hello! 来自 百度搜索 的朋友
你是搜索 ", " 找到的我吗?"], "so": ["Hello! 来自 360搜索 的朋友
你是搜索 ", " 找到的我吗?"], "google": ["Hello! 来自 谷歌搜索 的朋友
欢迎阅读『", "』", " - "], "default": ["Hello! 来自 ", " 的朋友"], "none": ["欢迎阅读『", "』", " - "] }, "referrer_hostname": { "example.com": ["示例网站"], "www.fghrsh.net": ["FGHRSH 的博客"] }, "model_message": { "1": ["来自 Potion Maker 的 Pio 酱 ~"], "2": ["来自 Potion Maker 的 Tia 酱 ~"] }, }, "mouseover": [ { "selector": ".container a[href^='http']", "text": ["要看看 {text} 么?"] }, { "selector": ".fui-home", "text": ["点击前往首页,想回到上一页可以使用浏览器的后退功能哦"] }, { "selector": ".fui-chat", "text": ["一言一语,一颦一笑。一字一句,一颗赛艇。"] }, { "selector": ".fui-eye", "text": ["嗯··· 要切换 看板娘 吗?"] }, { "selector": ".fui-user", "text": ["喜欢换装 Play 吗?"] }, { "selector": ".fui-info-circle", "text": ["这里有关于我的信息呢"] }, { "selector": "#tor_show", "text": ["翻页比较麻烦吗,点击可以显示这篇文章的目录呢"] }, { "selector": "#comment_go", "text": ["想要去评论些什么吗?"] }, { "selector": "#night_mode", "text": ["深夜时要爱护眼睛呀"] }, { "selector": "#qrcode", "text": ["手机扫一下就能继续看,很方便呢"] }, { "selector": ".comment_reply", "text": ["要吐槽些什么呢"] }, { "selector": "#back-to-top", "text": ["回到开始的地方吧"] }, { "selector": "#author", "text": ["该怎么称呼你呢"] }, { "selector": "#mail", "text": ["留下你的邮箱,不然就是无头像人士了"] }, { "selector": "#url", "text": ["你的家在哪里呢,好让我去参观参观"] }, { "selector": "#textarea", "text": ["认真填写哦,垃圾评论是禁止事项"] }, { "selector": ".OwO-logo", "text": ["要插入一个表情吗"] }, { "selector": "#csubmit", "text": ["要[提交]^(Commit)了吗,首次评论需要审核,请耐心等待~"] }, { "selector": ".ImageBox", "text": ["点击图片可以放大呢"] }, { "selector": "input[name=s]", "text": ["找不到想看的内容?搜索看看吧"] }, { "selector": ".previous", "text": ["去上一页看看吧"] }, { "selector": ".next", "text": ["去下一页看看吧"] }, { "selector": ".dropdown-toggle", "text": ["这里是菜单"] }, { "selector": "c-player a.play-icon", "text": ["想要听点音乐吗"] }, { "selector": "c-player div.time", "text": ["在这里可以调整播放进度呢"] }, { "selector": "c-player div.volume", "text": ["在这里可以调整音量呢"] }, { "selector": "c-player div.list-button", "text": ["播放列表里都有什么呢"] }, { "selector": "c-player div.lyric-button", "text": ["有歌词的话就能跟着一起唱呢"] }, ], "seasons": [ { "date": "01/01", "text": ["元旦了呢,新的一年又开始了,今年是{year}年~"] }, { "date": "02/14", "text": ["又是一年情人节,{year}年找到对象了嘛~"] }, { "date": "03/08", "text": ["今天是妇女节!"] }, { "date": "03/12", "text": ["今天是植树节,要保护环境呀"] }, { "date": "04/01", "text": ["悄悄告诉你一个秘密~今天是愚人节,不要被骗了哦~"] }, { "date": "05/01", "text": ["今天是五一劳动节,计划好假期去哪里了吗~"] }, { "date": "06/01", "text": ["儿童节了呢,快活的时光总是短暂,要是永远长不大该多好啊…"] }, { "date": "09/03", "text": ["中国人民抗日战争胜利纪念日,铭记历史、缅怀先烈、珍爱和平、开创未来。"] }, { "date": "09/10", "text": ["教师节,在学校要给老师问声好呀~"] }, { "date": "10/01", "text": ["国庆节,新中国已经成立69年了呢"] }, { "date": "11/05-11/12", "text": ["今年的双十一是和谁一起过的呢~"] }, { "date": "12/20-12/31", "text": ["这几天是圣诞节,主人肯定又去剁手买买买了~"] } ] }; // ========== 全局变量 ========== let aiConfig = {}; let gestureConfig = {}; let touchStartX = 0; let touchStartY = 0; let touchEndX = 0; let touchEndY = 0; let settingsPanel = null; let currentPage = 0; const totalPages = 2; let isDragging = false; let mouseStartX = 0; let mouseStartY = 0; let mouseEndX = 0; let mouseEndY = 0; let hasMoved = false; let lastClickTime = 0; let conversationHistory = []; // 对话历史 let clickTimeout = null; // 点击延迟计时器 let clickCount = 0; // 点击次数计数器 const DOUBLE_CLICK_DELAY = 300; // 双击检测延迟(毫秒) // ========== 上下文记忆配置 ========== const MAX_CONTEXT_SIZE = 65536; // 64KB const CONTEXT_RESERVE_RATIO = 0.8; // 当超过80%时开始清理 // ========== 消息显示相关变量 ========== let messageHideTimer = null; let mouseOverMessage = false; const MESSAGE_HIDE_DELAY = 5000; // 默认停留时间5秒 const MOUSE_LEAVE_DELAY = 3000; // 鼠标移开后3秒消失 // ========== 本地 showMessage 函数(覆盖外部库) ========== window.showMessage = function(text, duration = MESSAGE_HIDE_DELAY, noReset = false) { const $tips = $('.waifu-tips'); // 清除之前的隐藏计时器 if (messageHideTimer) { clearTimeout(messageHideTimer); messageHideTimer = null; } // 如果不是 noReset 模式,清空之前的内容 if (!noReset) { $tips.html(text); } else { $tips.append(text); } // 显示消息 $tips.addClass('active'); // 设置自动隐藏计时器 messageHideTimer = setTimeout(function() { // 如果鼠标正在悬停,不隐藏 if (!mouseOverMessage) { hideMessage(); } }, duration); }; // 隐藏消息 function hideMessage() { const $tips = $('.waifu-tips'); $tips.removeClass('active'); if (messageHideTimer) { clearTimeout(messageHideTimer); messageHideTimer = null; } } // ========== 存储管理 ========== function loadConfig() { aiConfig = GM_getValue('aiConfig', DEFAULT_AI_CONFIG); gestureConfig = GM_getValue('gestureConfig', DEFAULT_GESTURE_CONFIG); } function saveConfig() { GM_setValue('aiConfig', aiConfig); GM_setValue('gestureConfig', gestureConfig); } // ========== 对话历史管理 ========== function calculateConversationSize() { const jsonString = JSON.stringify(conversationHistory); return new Blob([jsonString]).size; } function manageConversationHistory() { const currentSize = calculateConversationSize(); const threshold = MAX_CONTEXT_SIZE * CONTEXT_RESERVE_RATIO; console.log('[Live2D] 当前对话历史大小:', currentSize, 'bytes, 限制:', MAX_CONTEXT_SIZE, 'bytes'); if (currentSize > threshold) { console.log('[Live2D] 对话历史超出限制,开始清理旧对话...'); // 从最旧的对话开始删除,直到大小在限制范围内 while (conversationHistory.length > 0 && calculateConversationSize() > threshold) { // 删除最早的2条消息(一轮对话包含用户和助手) conversationHistory.shift(); // 删除用户消息 conversationHistory.shift(); // 删除助手消息 console.log('[Live2D] 已删除旧对话,剩余对话轮数:', conversationHistory.length / 2); } // 保存清理后的对话历史到localStorage saveConversationHistory(); } } function saveConversationHistory() { try { const historyJson = JSON.stringify(conversationHistory); GM_setValue('live2d_conversation_history', historyJson); console.log('[Live2D] 对话历史已保存到 GM_setValue,大小:', calculateConversationSize(), 'bytes'); } catch (e) { console.error('[Live2D] 保存对话历史失败:', e); // 如果保存失败,可能是GM_setValue空间不足,清理更多对话 if (conversationHistory.length > 2) { conversationHistory = conversationHistory.slice(-4); // 只保留最后2轮对话 saveConversationHistory(); } } } function loadConversationHistory() { try { const historyJson = GM_getValue('live2d_conversation_history', null); if (historyJson) { conversationHistory = JSON.parse(historyJson); console.log('[Live2D] 对话历史已从 GM_setValue 加载,对话轮数:', conversationHistory.length / 2, '大小:', calculateConversationSize(), 'bytes'); } } catch (e) { console.error('[Live2D] 加载对话历史失败:', e); conversationHistory = []; } } function shouldLoad() { if (!DEFAULT_CONFIG.enabled) return false; const hostname = window.location.hostname; if (DEFAULT_CONFIG.useBlacklist) { return !DEFAULT_CONFIG.blacklist.some(site => hostname.includes(site)); } return true; } if (!shouldLoad()) { console.log('[Live2D] 已禁用或在黑名单中'); return; } // 防止在 iframe 中运行 if (window.self !== window.top) { console.log('[Live2D] 检测到 iframe 环境,跳过加载'); return; } console.log('[Live2D] 简化版看板娘开始加载...'); loadConfig(); // ========== 网页文本检索功能 ========== function extractPageContent() { let content = ''; // 提取页面标题 content += '页面标题: ' + document.title + '\n\n'; // 提取主要文本内容 const mainElements = document.querySelectorAll('h1, h2, h3, h4, h5, h6, p, article, main, .content, .article, .post'); mainElements.forEach(el => { if (el.textContent.trim()) { content += el.tagName + ': ' + el.textContent.trim() + '\n\n'; } }); // 限制内容长度 const maxLength = 20000; if (content.length > maxLength) { content = content.substring(0, maxLength) + '...'; } return content; } // ========== AI 对话功能 ========== async function callAI(message, context = '', history = []) { if (!aiConfig.apiKey) { if (typeof showMessage === 'function') { showMessage('请先在设置中配置 AI API', 3000); } return null; } let fullMessage = ''; if (context) { fullMessage = `网页内容上下文:\n${context}\n\n用户问题:${message}`; } else { fullMessage = message; } // 构建消息数组,包含系统提示词、历史对话和当前消息 const messages = [ { role: 'system', content: aiConfig.systemPrompt } ]; // 添加对话历史(限制最近的10轮对话,避免token过多) const maxHistory = 10; const recentHistory = history.slice(-maxHistory * 2); // 每轮对话包含用户和助手两条消息 for (const msg of recentHistory) { messages.push(msg); } // 添加当前消息 messages.push({ role: 'user', content: fullMessage }); try { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: aiConfig.apiUrl, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${aiConfig.apiKey}` }, data: JSON.stringify({ model: aiConfig.model, messages: messages, max_tokens: aiConfig.maxTokens, temperature: aiConfig.temperature }), onload: function(response) { if (response.status === 200) { const data = JSON.parse(response.responseText); const reply = data.choices[0].message.content; resolve(reply); } else { console.error('[Live2D] AI API 错误:', response.status, response.responseText); if (typeof showMessage === 'function') { showMessage('AI 响应错误,请检查配置', 3000); } reject(new Error('API error')); } }, onerror: function(error) { console.error('[Live2D] AI 请求错误:', error); if (typeof showMessage === 'function') { showMessage('AI 请求失败,请检查网络', 3000); } reject(error); } }); }); } catch (error) { console.error('[Live2D] AI 调用错误:', error); return null; } } // ========== 滑动手势处理 ========== function handleTouchStart(e) { touchStartX = e.touches[0].clientX; touchStartY = e.touches[0].clientY; } function handleTouchEnd(e) { touchEndX = e.changedTouches[0].clientX; touchEndY = e.changedTouches[0].clientY; handleSwipe(); } // ========== 鼠标拖动事件处理 ========== function handleMouseDown(e) { isDragging = true; hasMoved = false; mouseStartX = e.clientX; mouseStartY = e.clientY; } function handleMouseMove(e) { if (isDragging) { mouseEndX = e.clientX; mouseEndY = e.clientY; const diffX = Math.abs(mouseEndX - mouseStartX); const diffY = Math.abs(mouseEndY - mouseStartY); if (diffX > 5 || diffY > 5) { hasMoved = true; } } } function handleMouseUp(e) { if (isDragging) { isDragging = false; if (hasMoved) { mouseEndX = e.clientX; mouseEndY = e.clientY; handleMouseDrag(); } } } function handleMouseDrag() { const diffX = mouseEndX - mouseStartX; const diffY = mouseEndY - mouseStartY; const threshold = 50; // 判断拖动方向 if (Math.abs(diffX) > Math.abs(diffY)) { // 水平拖动 if (diffX > threshold && gestureConfig.enableSwipeRight) { // 向右拖动 - 切换模型 loadOtherModel(); } else if (diffX < -threshold && gestureConfig.enableSwipeLeft) { // 向左拖动 - 切换衣服 loadRandTextures(); } } else { // 垂直拖动 if (diffY > threshold && gestureConfig.enableSwipeDown) { // 向下拖动 - 打开设置 openSettingsPanel(); } } } // ========== 点击对话功能 ========== async function handleClick() { const now = Date.now(); // 检查距离上次单击是否超过5秒 if (now - lastClickTime < 5000) { console.log('[Live2D] 单击触发限制中,距离上次单击不足5秒'); return; } // 更新最后单击时间 lastClickTime = now; const pageContent = extractPageContent(); if (typeof showMessage === 'function') { showMessage('正在思考...', 20000); } try { const response = await callAI('回答内容控制在10-150字', pageContent); if (response && typeof showMessage === 'function') { showMessage(response, 3000); } } catch (error) { console.error('[Live2D] 对话错误:', error); } } // ========== 双击显示输入框 ========== function handleDoubleClick() { // 如果正在拖动,不触发双击 if (hasMoved) return; // 重置最后单击时间,避免影响后续单击 lastClickTime = 0; const input = $('#waifu-chat-input'); if (input.hasClass('show')) { input.removeClass('show'); setTimeout(() => input.blur(), 300); } else { input.addClass('show'); setTimeout(() => input.focus(), 300); } } // ========== 处理输入框提交 ========== async function handleInputSubmit(message) { if (!message || !message.trim()) return; // 检查是否是清空对话命令 const clearCommands = ['清空对话', 'clear', '重置对话', 'reset']; if (clearCommands.includes(message.trim().toLowerCase())) { conversationHistory = []; GM_deleteValue('live2d_conversation_history'); if (typeof showMessage === 'function') { showMessage('对话历史已清空~', 2000); } $('#waifu-chat-input').val('').removeClass('show'); return; } if (typeof showMessage === 'function') { showMessage('正在思考...', 2000); } try { // 传递对话历史给AI const response = await callAI(message, '', conversationHistory); if (response && typeof showMessage === 'function') { showMessage(response, 5000); // 将用户消息和AI回复添加到对话历史 conversationHistory.push({ role: 'user', content: message }); conversationHistory.push({ role: 'assistant', content: response }); console.log('[Live2D] 对话历史已更新,当前对话轮数:', conversationHistory.length / 2); // 管理对话历史大小 manageConversationHistory(); // 保存对话历史到localStorage saveConversationHistory(); } } catch (error) { console.error('[Live2D] 对话错误:', error); } // 清空输入框并隐藏 $('#waifu-chat-input').val('').removeClass('show'); } function handleSwipe() { const diffX = touchEndX - touchStartX; const diffY = touchEndY - touchStartY; const threshold = 50; // 判断滑动方向 if (Math.abs(diffX) > Math.abs(diffY)) { // 水平滑动 if (diffX > threshold && gestureConfig.enableSwipeRight) { // 向右滑动 - 切换模型 loadOtherModel(); } else if (diffX < -threshold && gestureConfig.enableSwipeLeft) { // 向左滑动 - 切换衣服 loadRandTextures(); } } else { // 垂直滑动 if (diffY > threshold && gestureConfig.enableSwipeDown) { // 向下滑动 - 打开设置 openSettingsPanel(); } } } // ========== Live2D 模型切换功能 ========== function loadOtherModel() { const modelId = GM_getValue('modelId', DEFAULT_MODEL_CONFIG.modelId) || live2d_settings.modelId; const modelRandMode = 'switch'; console.log('[Live2D] 开始切换模型,当前模型ID:', modelId); if (typeof showMessage === 'function') { showMessage('正在切换看板娘...', 2000); } $.ajax({ cache: modelRandMode === 'switch', url: `https://live2d.fghrsh.net/api/${modelRandMode}/?id=${modelId}`, dataType: "json", success: function(result) { if (result && result.model) { const newModelId = result.model.id; const message = result.model.message || '新的看板娘来啦~'; console.log('[Live2D] 切换模型成功,新模型ID:', newModelId); // 保存到 GM_setValue(跨域名共享) GM_setValue('modelId', newModelId); GM_setValue('modelTexturesId', 0); console.log('[Live2D] 已保存模型设置到 GM_setValue - 模型ID:', newModelId, '材质ID: 0'); if (typeof loadModel === 'function') { loadModel(newModelId, 0); } else if (typeof loadlive2d === 'function') { loadlive2d('live2d', `https://live2d.fghrsh.net/api/get/?id=${newModelId}-0`); } if (typeof showMessage === 'function') { showMessage(message, 3000, true); } } }, error: function() { console.error('[Live2D] 切换模型失败'); if (typeof showMessage === 'function') { showMessage('切换失败了...', 3000); } } }); } function loadRandTextures() { const modelId = GM_getValue('modelId', DEFAULT_MODEL_CONFIG.modelId) || live2d_settings.modelId; const modelTexturesId = GM_getValue('modelTexturesId', DEFAULT_MODEL_CONFIG.modelTexturesId) || 0; const modelTexturesRandMode = 'rand'; console.log('[Live2D] 开始换装,当前模型ID:', modelId, '材质ID:', modelTexturesId); if (typeof showMessage === 'function') { showMessage('正在换装中...', 2000); } $.ajax({ cache: modelTexturesRandMode === 'switch', url: `https://live2d.fghrsh.net/api/${modelTexturesRandMode}_textures/?id=${modelId}-${modelTexturesId}`, dataType: "json", success: function(result) { if (result && result.textures) { const newTexturesId = result.textures.id; console.log('[Live2D] 换装成功,新材质ID:', newTexturesId); // 保存到 GM_setValue(跨域名共享) GM_setValue('modelTexturesId', newTexturesId); console.log('[Live2D] 已保存换装设置到 GM_setValue - 模型ID:', modelId, '材质ID:', newTexturesId); if (newTexturesId === 1 && (modelTexturesId === 1 || modelTexturesId === 0)) { if (typeof showMessage === 'function') { showMessage('我还没有其他衣服呢', 3000, true); } } else { if (typeof showMessage === 'function') { showMessage('我的新衣服好看嘛', 3000, true); } } if (typeof loadModel === 'function') { loadModel(modelId, newTexturesId); } else if (typeof loadlive2d === 'function') { loadlive2d('live2d', `https://live2d.fghrsh.net/api/get/?id=${modelId}-${newTexturesId}`); } } }, error: function() { console.error('[Live2D] 换装失败'); if (typeof showMessage === 'function') { showMessage('换装失败了...', 3000); } } }); } // ========== 设置面板 ========== function createSettingsPanel() { const panelHtml = `

看板娘设置

🤖 AI API 设置

👗 换装与换模型

模型: 1 材质: 0 对话: 0轮
`; $('body').append(panelHtml); initSettingsPanel(); } function initSettingsPanel() { settingsPanel = $('#live2d-settings-panel'); // 关闭按钮 $('.close-btn').click(function() { closeSettingsPanel(); }); // 页面切换 $('.next-btn').click(function() { if (currentPage < totalPages - 1) { changePage(currentPage + 1); } }); $('.prev-btn').click(function() { if (currentPage > 0) { changePage(currentPage - 1); } }); // 页面指示器 $('.page-dot').click(function() { const page = parseInt($(this).data('page')); changePage(page); }); // AI 设置保存 $('.save-btn').click(function() { aiConfig.apiKey = $('#api-key').val(); aiConfig.apiUrl = $('#api-url').val() || DEFAULT_AI_CONFIG.apiUrl; aiConfig.model = $('#api-model').val() || DEFAULT_AI_CONFIG.model; aiConfig.systemPrompt = $('#system-prompt').val() || DEFAULT_AI_CONFIG.systemPrompt; aiConfig.maxTokens = parseInt($('#max-tokens').val()) || DEFAULT_AI_CONFIG.maxTokens; aiConfig.temperature = parseFloat($('#temperature').val()) || DEFAULT_AI_CONFIG.temperature; saveConfig(); if (typeof showMessage === 'function') { showMessage('AI 设置已保存', 2000); } }); // 预览模型 $('#preview-model').click(function() { const modelId = $('#model-id').val(); console.log('[Live2D] 预览模型 - 模型ID:', modelId); if (typeof loadModel === 'function') { loadModel(modelId, 0); GM_setValue('modelId', modelId); GM_setValue('modelTexturesId', 0); console.log('[Live2D] 已保存模型设置到 GM_setValue - 模型ID:', modelId, '材质ID: 0'); if (typeof showMessage === 'function') { showMessage(`正在预览模型 ${modelId}`, 2000); } } else if (typeof loadlive2d === 'function') { GM_setValue('modelId', modelId); GM_setValue('modelTexturesId', 0); console.log('[Live2D] 已保存模型设置到 GM_setValue - 模型ID:', modelId, '材质ID: 0'); loadlive2d('live2d', `https://live2d.fghrsh.net/api/get/?id=${modelId}-0`); if (typeof showMessage === 'function') { showMessage(`正在预览模型 ${modelId}`, 2000); } } }); // 预览材质 $('#preview-textures').click(function() { const modelId = $('#model-id').val(); const texturesId = $('#textures-id').val(); console.log('[Live2D] 预览材质 - 模型ID:', modelId, '材质ID:', texturesId); if (typeof loadModel === 'function') { loadModel(modelId, texturesId); GM_setValue('modelId', modelId); GM_setValue('modelTexturesId', texturesId); console.log('[Live2D] 已保存换装设置到 GM_setValue - 模型ID:', modelId, '材质ID:', texturesId); if (typeof showMessage === 'function') { showMessage(`正在预览材质 ${texturesId}`, 2000); } } else if (typeof loadlive2d === 'function') { GM_setValue('modelId', modelId); GM_setValue('modelTexturesId', texturesId); console.log('[Live2D] 已保存换装设置到 GM_setValue - 模型ID:', modelId, '材质ID:', texturesId); loadlive2d('live2d', `https://live2d.fghrsh.net/api/get/?id=${modelId}-${texturesId}`); if (typeof showMessage === 'function') { showMessage(`正在预览材质 ${texturesId}`, 2000); } } }); // 应用设置 $('.apply-btn').click(function() { const modelId = $('#model-id').val(); const texturesId = $('#textures-id').val(); console.log('[Live2D] 应用设置 - 模型ID:', modelId, '材质ID:', texturesId); if (typeof loadModel === 'function') { loadModel(modelId, texturesId); GM_setValue('modelId', modelId); GM_setValue('modelTexturesId', texturesId); } else if (typeof loadlive2d === 'function') { GM_setValue('modelId', modelId); GM_setValue('modelTexturesId', texturesId); loadlive2d('live2d', `https://live2d.fghrsh.net/api/get/?id=${modelId}-${texturesId}`); } console.log('[Live2D] 已保存模型和换装设置到 GM_setValue(跨域名共享)'); updateCurrentInfo(); if (typeof showMessage === 'function') { showMessage('设置已应用', 2000); } }); // 清空对话历史 $('.clear-history-btn').click(function() { conversationHistory = []; GM_deleteValue('live2d_conversation_history'); updateCurrentInfo(); if (typeof showMessage === 'function') { showMessage('对话历史已清空', 2000); } }); // 填充当前值 loadSettingsValues(); // 初始化页面按钮状态 $('.prev-btn').prop('disabled', true); $('.next-btn').prop('disabled', false); } function loadSettingsValues() { // AI 设置 $('#api-key').val(aiConfig.apiKey); $('#api-url').val(aiConfig.apiUrl); $('#api-model').val(aiConfig.model); $('#system-prompt').val(aiConfig.systemPrompt); $('#max-tokens').val(aiConfig.maxTokens); $('#temperature').val(aiConfig.temperature); // 模型和材质设置(从 GM_getValue 读取,跨域名共享) const currentModelId = GM_getValue('modelId', DEFAULT_MODEL_CONFIG.modelId); const currentTexturesId = GM_getValue('modelTexturesId', DEFAULT_MODEL_CONFIG.modelTexturesId); $('#model-id').val(currentModelId); $('#textures-id').val(currentTexturesId); updateCurrentInfo(); } function changePage(page) { currentPage = page; $('.settings-page').removeClass('active'); $(`.settings-page[data-page="${page}"]`).addClass('active'); $('.page-dot').removeClass('active'); $(`.page-dot[data-page="${page}"]`).addClass('active'); // 更新按钮状态 $('.prev-btn').prop('disabled', page === 0); $('.next-btn').prop('disabled', page === totalPages - 1); } function updateCurrentInfo() { const modelId = GM_getValue('modelId', DEFAULT_MODEL_CONFIG.modelId); const texturesId = GM_getValue('modelTexturesId', DEFAULT_MODEL_CONFIG.modelTexturesId); const historyCount = Math.floor(conversationHistory.length / 2); $('#current-model').text(`模型: ${modelId}`); $('#current-textures').text(`材质: ${texturesId}`); $('#chat-history-count').text(`对话: ${historyCount}轮`); } function openSettingsPanel() { if (!settingsPanel) { createSettingsPanel(); } loadSettingsValues(); settingsPanel.addClass('show'); } function closeSettingsPanel() { if (settingsPanel) { settingsPanel.removeClass('show'); } } // ========== 样式 ========== GM_addStyle(` /* 看板娘基础样式 */ .waifu { position: fixed; bottom: 0; right: 0; z-index: 999999 !important; font-size: 0; transform: translateY(3px); opacity: 0; transition: opacity 0.5s ease-in-out, transform 0.3s ease-in-out; touch-action: none; } .waifu.loaded { opacity: 1; } .waifu:hover { transform: translateY(0); } .waifu-tips { opacity: 0; margin: -50px 20px; padding: 8px 14px; border: 1px solid rgb(211, 211, 211); border-radius: 12px; background-color: rgb(255, 255, 255); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); position: absolute; min-width: 80px !important; max-width: 300px !important; width: fit-content !important; height: auto !important; font-size: 14px; font-weight: 600; line-height: 1.4; color: rgb(0, 0, 0); transition: opacity 0.3s ease-in-out, transform 0.3s; word-wrap: break-word; white-space: normal; display: inline-block; text-align: center; z-index: 1000000; } /* 对话框小三角箭头 */ .waifu-tips::before { content: ""; position: absolute; width: 16px; height: 16px; bottom: -8px; right: 30px; transform: rotate(45deg); background-color: rgb(255, 255, 255); border-right: 1px solid rgb(211, 211, 211); border-bottom: 1px solid rgb(211, 211, 211); } /* 显示动画 */ .waifu-tips.active { opacity: 1; transform: translateY(-5px); } .waifu-tool { display: none; color: #aaa; top: 5px; right: 10px; position: absolute; font-size: 14px; } .waifu:hover .waifu-tool { display: block; } .waifu-tool span { display: block; cursor: pointer; color: #5b6c7d; transition: 0.2s; margin: 5px 0; line-height: 20px; } .waifu-tool span:hover { color: #34495e; } .waifu #live2d { position: relative; } /* 对话输入框样式 */ .waifu-chat-input { display: none; position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); width: 280px; height: 25px; padding: 0 10px; background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: 12px; font-size: 13px; color: #333; outline: none; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); z-index: 1000001; transition: all 0.3s ease; } .waifu-chat-input.show { display: block; animation: fadeInUp 0.3s ease; } .waifu-chat-input:focus { background: rgba(255, 255, 255, 0.95); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); border-color: rgba(76, 175, 80, 0.3); } .waifu-chat-input::placeholder { color: #999; font-size: 12px; } @keyframes fadeInUp { from { opacity: 0; transform: translateX(-50%) translateY(10px); } to { opacity: 1; transform: translateX(-50%) translateY(0); } } /* 加载动画 */ .waifu-loading { position: absolute; bottom: 120px; left: 50%; transform: translateX(-50%); z-index: 2; display: flex; flex-direction: column; align-items: center; gap: 10px; } .waifu-loading svg { width: 60px; height: 60px; } .waifu-loading svg polyline { fill: none; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; } .waifu-loading svg polyline#back { fill: none; stroke: #ff4d5033; } .waifu-loading svg polyline#front { fill: none; stroke: #ff4d4f; stroke-dasharray: 48, 144; stroke-dashoffset: 192; animation: dash_682 1.4s linear infinite; } @keyframes dash_682 { 72.5% { opacity: 0; } to { stroke-dashoffset: 0; } } .waifu-loading-text { font-size: 12px; color: #ff4d4f; font-weight: bold; text-shadow: 0 0 5px rgba(255, 77, 79, 0.5); } @keyframes shake { 2% { transform: translate(0.5px, -1.5px) rotate(-0.5deg); } 4% { transform: translate(0.5px, 1.5px) rotate(1.5deg); } 50% { transform: translate(-1.5px, 1.5px) rotate(0.5deg); } 0%, 100% { transform: translate(0, 0) rotate(0); } } /* 设置面板样式 */ .live2d-settings-panel { position: fixed; bottom: 220px; right: 20px; transform: scale(0.9); width: 280px; max-height: 80vh; background: rgba(255, 255, 255, 0.7); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border-radius: 16px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); z-index: 1000001; display: flex; flex-direction: column; opacity: 0; visibility: hidden; transition: all 0.3s ease; } .live2d-settings-panel.show { opacity: 1; visibility: visible; transform: scale(1); } .settings-header { display: flex; justify-content: space-between; align-items: center; padding: 20px; border-bottom: 1px solid #e0e0e0; } .settings-header h3 { margin: 0; font-size: 18px; color: #333; } .close-btn { background: none; border: none; font-size: 24px; cursor: pointer; color: #999; padding: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.2s; } .close-btn:hover { background: #f0f0f0; color: #333; } .settings-pages { flex: 1; overflow-y: auto; padding: 20px; position: relative; } .settings-page { display: none; animation: fadeIn 0.3s ease; } .settings-page.active { display: block; } @keyframes fadeIn { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } } .settings-page h4 { margin: 0 0 20px 0; font-size: 16px; color: #555; border-left: 3px solid #4CAF50; padding-left: 10px; } .setting-item { margin-bottom: 15px; } .setting-item label { display: block; margin-bottom: 5px; font-size: 14px; color: #666; font-weight: 500; } .setting-item input[type="text"], .setting-item input[type="password"], .setting-item input[type="number"], .setting-item textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; box-sizing: border-box; transition: border-color 0.2s; } .setting-item input:focus, .setting-item textarea:focus { outline: none; border-color: #4CAF50; } .setting-item textarea { resize: vertical; min-height: 80px; } .setting-item.toggle-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; } .setting-item.toggle-item label { margin-bottom: 0; } .setting-item.toggle-item input[type="checkbox"] { width: 20px; height: 20px; cursor: pointer; } .save-btn { width: 100%; padding: 12px; background: #4CAF50; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s; margin-top: 10px; } .save-btn:hover { background: #45a049; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3); } .apply-btn { width: 100%; padding: 12px; background: #2196F3; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s; margin-top: 10px; } .apply-btn:hover { background: #1976D2; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(33, 150, 243, 0.3); } .clear-history-btn { width: 100%; padding: 12px; background: #F44336; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s; margin-top: 10px; } .clear-history-btn:hover { background: #D32F2F; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(244, 67, 54, 0.3); } .preview-buttons { display: flex; gap: 10px; } .preview-btn { flex: 1; padding: 10px; background: #FF9800; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s; } .preview-btn:hover { background: #F57C00; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(255, 152, 0, 0.3); } .current-info { display: flex; justify-content: space-around; padding: 10px; background: #f5f5f7; border-radius: 8px; } .current-info span { font-size: 13px; color: #666; font-weight: 500; } .settings-footer { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; border-top: 1px solid #e0e0e0; background: #fafafa; border-radius: 0 0 16px 16px; } .prev-btn, .next-btn { padding: 8px 16px; background: #fff; color: #666; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; cursor: pointer; transition: all 0.2s; } .prev-btn:hover:not(:disabled), .next-btn:hover:not(:disabled) { background: #f0f0f0; border-color: #ccc; } .prev-btn:disabled, .next-btn:disabled { opacity: 0.5; cursor: not-allowed; } .page-indicator { display: flex; gap: 8px; } .page-dot { width: 8px; height: 8px; border-radius: 50%; background: #ddd; cursor: pointer; transition: all 0.2s; } .page-dot.active { background: #4CAF50; transform: scale(1.2); } .page-dot:hover { background: #45a049; } `); // ========== 初始化 ========== function init() { console.log('[Live2D] 开始初始化...'); // 加载对话历史 loadConversationHistory(); // 加载动画 SVG const loadingSvg = DEFAULT_CONFIG.showLoadingTip ? `
加载中...
` : ''; const waifuHtml = `
${loadingSvg}
`; // 检查是否已经存在看板娘,避免重复创建 if ($('.waifu').length > 0) { console.log('[Live2D] 看板娘已存在,跳过创建'); return; } $('body').append(waifuHtml); // 添加触摸事件监听 const waifuContainer = $('#waifu-container')[0]; waifuContainer.addEventListener('touchstart', handleTouchStart, { passive: true }); waifuContainer.addEventListener('touchend', handleTouchEnd, { passive: true }); // 添加鼠标拖动事件监听 waifuContainer.addEventListener('mousedown', handleMouseDown); document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); // 添加点击事件 - 使用延迟检测机制实现"优先检测双击然后检测单击" waifuContainer.addEventListener('click', function(e) { // 只在点击画布、没有拖动时处理 if (!hasMoved && (e.target.id === 'live2d' || e.target.closest('#live2d'))) { clickCount++; // 清除之前的延迟计时器 if (clickTimeout) { clearTimeout(clickTimeout); } // 设置新的延迟计时器 clickTimeout = setTimeout(function() { // 延迟到期时,根据点击次数执行相应操作 if (clickCount === 1) { // 单击 - 触发对话(仅当输入框未显示时) if (!$('#waifu-chat-input').hasClass('show')) { handleClick(); } } else if (clickCount >= 2) { // 双击 - 显示/隐藏输入框 handleDoubleClick(); } // 重置点击计数 clickCount = 0; clickTimeout = null; }, DOUBLE_CLICK_DELAY); } }); // 添加输入框事件 $('#waifu-chat-input').on('keypress', function(e) { if (e.which === 13) { // Enter 键 e.preventDefault(); handleInputSubmit($(this).val()); } }); // 点击输入框外部时隐藏输入框 $(document).on('click', function(e) { if (!$(e.target).closest('.waifu').length) { $('#waifu-chat-input').removeClass('show'); } }); // ========== 消息提示框鼠标悬停检测 ========== // 鼠标进入消息提示框时,清除隐藏计时器 $('.waifu-tips').on('mouseenter', function() { mouseOverMessage = true; if (messageHideTimer) { clearTimeout(messageHideTimer); messageHideTimer = null; } }); // 鼠标离开消息提示框时,3秒后自动隐藏 $('.waifu-tips').on('mouseleave', function() { mouseOverMessage = false; if (messageHideTimer) { clearTimeout(messageHideTimer); messageHideTimer = null; } messageHideTimer = setTimeout(function() { hideMessage(); }, MOUSE_LEAVE_DELAY); }); if (typeof initModel !== 'function') { console.error('[Live2D] initModel 函数未定义'); $('.waifu-loading').html('
加载失败
'); return; } if (typeof live2d_settings === 'undefined') { console.error('[Live2D] live2d_settings 未定义'); $('.waifu-loading').html('
加载失败
'); return; } console.log('[Live2D] 配置参数...'); // 从 GM_getValue 读取之前的模型和换装设置(跨域名共享) const savedModelId = GM_getValue('modelId', DEFAULT_MODEL_CONFIG.modelId); const savedTexturesId = GM_getValue('modelTexturesId', DEFAULT_MODEL_CONFIG.modelTexturesId); live2d_settings['modelId'] = savedModelId; live2d_settings['modelTexturesId'] = savedTexturesId; live2d_settings['waifuEdgeSide'] = 'right:0'; live2d_settings['waifuDraggable'] = 'axis-x'; live2d_settings['modelStorage'] = true; console.log('[Live2D] 加载模型设置 - 模型ID:', live2d_settings['modelId'], '材质ID:', live2d_settings['modelTexturesId']); console.log('[Live2D] 配置跨域名共享,将在所有网站生效'); console.log('[Live2D] 调用 initModel...'); // 使用本地配置,不再从远程加载 initModel(WAIFU_TIPS_CONFIG); // 强制覆盖对话框样式,使其自动调整大小 setTimeout(() => { $('.waifu-tips').css({ 'width': 'fit-content', 'height': 'auto', 'min-width': '80px', 'max-width': '300px' }); }, 100); setTimeout(() => { $('.waifu-loading').fadeOut(300, function() { $(this).remove(); }); $('.waifu').addClass('loaded'); console.log('[Live2D] 看板娘加载完成!'); // ========== 工具栏按钮事件绑定 ========== // 切换看板娘按钮(切换模型) $('.waifu-tool .el-icon-user').hover(function() { if (typeof showMessage === 'function') { showMessage('要切换看板娘吗?', 3000); } }); $('.waifu-tool .el-icon-user').click(function() { loadOtherModel(); }); // 换装按钮(切换材质) $('.waifu-tool .el-icon-magic-stick').hover(function() { if (typeof showMessage === 'function') { showMessage('喜欢换装 Play 吗?', 3000); } }); $('.waifu-tool .el-icon-magic-stick').click(function() { loadRandTextures(); }); // 添加设置菜单命令 GM_registerMenuCommand('🎨 看板娘设置', openSettingsPanel); }, 2000); } setTimeout(() => { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } }, DEFAULT_CONFIG.loadDelay); console.log('[Live2D] 脚本加载完成,等待初始化...'); })();