// ==UserScript== // @name Live2D 看板娘 // @namespace https://scriptcat.org/zh-CN/users/162063 // @version 1.0.2 // @description Live2D 看板娘(2233娘,Potion Maker的Pio酱,Tia酱等等)+ 自定义台词 + 健康提醒 + 待办提醒 + 天气查询 + 每日一言 + 精美配置面板 // @author yyy. // @match *://*/* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @connect t.weather.itboy.net // @connect t.weather.sojson.com // @connect v.api.aa1.cn // @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== /* く__,.ヘヽ.    / ,ー、 〉      \ ', !-─‐-i / /´       /`ー'    L//`ヽ、      /  /,  /|  ,  ,    ',    イ  / /-‐/ i L_ ハ ヽ!  i     レ ヘ 7イ`ト  レ'ァ-ト、!ハ|  |      !,/7 '0'   ´0iソ|   |      |.从"  _   ,,,, / |./   |      レ'| i>.、,,__ _,.イ /  .i  |       レ'| | / k_7_/レ'ヽ, ハ. |        | |/i 〈|/  i ,.ヘ | i |       .|/ / i:   ヘ!  \ |         kヽ>、ハ   _,.ヘ、   /、!        !'〈//`T´', \ `'7'ーr'        レ'ヽL__|___i,___,ンレ|ノ          ト-,/ |___./          'ー'  !_,. */ (function() { 'use strict'; // ========== 默认配置 ========== const DEFAULT_CONFIG = { enabled: true, loadDelay: 1000, showLoadingTip: true, useBlacklist: true, blacklist: ['localhost', '127.0.0.1'], // 用户昵称 nickname: '宝宝', // 停靠位置设置 dockPosition: 'right', // 'left' 或 'right',记住停靠位置 // 天气设置 weather: { province: '广东', city: '广州' }, // 待办提醒设置 todos: { enabled: true, list: [] }, customMessages: { welcome: { morning: ["早上好呀{nickname}!新的一天开始了~", "早安{nickname}!今天也要加油哦!", "美好的早晨,{nickname}要吃早餐哦~"], noon: ["{nickname}中午好!该吃午饭啦~", "午餐时间到{nickname}!", "{nickname}中午了,休息一下吧~"], afternoon: ["{nickname}下午好!工作累了吗?", "午后时光,{nickname}要不要休息一下?", "{nickname}下午茶时间到~"], evening: ["{nickname}晚上好!今天过得怎么样?", "夜深了{nickname},早点休息哦~", "晚安{nickname},做个好梦~"], night: ["{nickname}这么晚还不睡吗?", "{nickname}熬夜对身体不好哦~", "夜猫子{nickname},该睡觉啦!"] }, idle: ["{nickname}在干嘛呢?", "{nickname}无聊了吗?", "{nickname}要不要聊聊天?", "{nickname}陪我玩会儿吧~"], click: ["不要戳我啦{nickname}!", "讨厌~{nickname}", "{nickname}再戳我就生气了!", "呜...好痒...{nickname}", "{nickname}你想干嘛?", "你看到我的小熊了吗?{nickname}", "再戳我可要报警了!", "110吗,这里有个变态一直在摸我(ó﹏ò。)"], }, healthReminders: { enabled: true, workingHours: { start: 9, end: 23 }, water: { enabled: true, interval: 30, messages: ["{nickname}该喝水啦!", "{nickname}记得补充水分哦~", "{nickname}喝口水休息一下吧!", "水是生命之源~{nickname}"] }, rest: { enabled: true, interval: 60, messages: ["{nickname}休息一下眼睛吧!", "{nickname}站起来活动活动~", "{nickname}工作一小时了,该休息啦!", "眺望远方,放松眼睛~{nickname}"] }, posture: { enabled: true, interval: 45, messages: ["{nickname}注意坐姿哦!", "{nickname}腰背挺直,保持好姿势~", "{nickname}久坐伤身,站起来走走吧~"] }, sleep: { enabled: true, time: 23, messages: ["{nickname}已经很晚了,该睡觉了!", "{nickname}熬夜对身体不好哦~", "{nickname}早点休息,明天才有精神~"] } } }; let config = Object.assign({}, DEFAULT_CONFIG, GM_getValue('live2d_config', {})); let reminderSystem = null; let todoSystem = null; // 消息优先级系统 let messageSystem = { isImportantMessageShowing: false, // 显示重要消息(一言、天气等) showImportant: function(text, timeout) { this.isImportantMessageShowing = true; if (typeof showMessage === 'function') { showMessage(text, timeout, true); } // 消息显示完毕后解除锁定 setTimeout(() => { this.isImportantMessageShowing = false; }, timeout || 5000); }, // 显示普通消息(鼠标悬停等) showNormal: function(text, timeout) { // 如果有重要消息正在显示,忽略普通消息 if (this.isImportantMessageShowing) { return; } if (typeof showMessage === 'function') { showMessage(text, timeout); } } }; function saveConfig() { GM_setValue('live2d_config', config); console.log('[Live2D] 配置已保存'); } function shouldLoad() { if (!config.enabled) return false; const hostname = window.location.hostname; if (config.useBlacklist) { return !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] 看板娘增强版开始加载...'); GM_registerMenuCommand('⚙ 看板娘设置', showConfigPanel); // ========== 引入 Element UI 样式 ========== const elementUILink = document.createElement('link'); elementUILink.rel = 'stylesheet'; elementUILink.href = 'https://unpkg.com/element-ui/lib/theme-chalk/index.css'; document.head.appendChild(elementUILink); // ========== 样式 ========== 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; } .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: 250px !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; } /* 对话框小三角箭头 - 相对于看板娘固定位置 */ .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-tool.left-side { left: 10px; right: auto; } .waifu #live2d { position: relative; } /* 加载动画 */ .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); } } /* Element UI 图标样式调整 */ .waifu-tool [class^="el-icon-"] { font-size: 16px; } /* 待办面板样式 */ .todo-panel { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 500px; max-height: 80vh; background: #FFFFFF; box-shadow: 0px 187px 75px rgba(0, 0, 0, 0.01), 0px 105px 63px rgba(0, 0, 0, 0.05), 0px 47px 47px rgba(0, 0, 0, 0.09), 0px 12px 26px rgba(0, 0, 0, 0.1); border-radius: 26px; z-index: 999999; overflow: hidden; } .todo-header { display: flex; justify-content: space-between; align-items: center; padding: 24px 28px 20px; border-bottom: 1px solid #f0f0f0; } .todo-title { font-size: 20px; color: #1d1d1f; font-weight: 600; letter-spacing: -0.3px; } .todo-title i { margin-right: 8px; color: #0071e3; } .todo-close { cursor: pointer; font-size: 28px; color: #86868b; line-height: 1; transition: color 0.2s; font-weight: 300; } .todo-close:hover { color: #1d1d1f; } .todo-body { max-height: calc(80vh - 200px); overflow-y: auto; padding: 20px 28px; } .todo-body::-webkit-scrollbar { width: 6px; } .todo-body::-webkit-scrollbar-track { background: transparent; } .todo-body::-webkit-scrollbar-thumb { background: #d1d1d6; border-radius: 3px; } .todo-item { display: flex; align-items: flex-start; padding: 16px; margin-bottom: 12px; background: #f5f5f7; border-radius: 12px; transition: all 0.2s; } .todo-item:hover { background: #e8e8ed; } .todo-checkbox { width: 20px; height: 20px; min-width: 20px; border: 2px solid #d1d1d6; border-radius: 50%; margin-right: 12px; cursor: pointer; transition: all 0.2s; } .todo-checkbox:hover { border-color: #34c759; } .todo-content { flex: 1; } .todo-text { font-size: 14px; color: #1d1d1f; margin-bottom: 6px; font-weight: 500; } .todo-time { font-size: 12px; color: #86868b; } .todo-delete { cursor: pointer; color: #ff3b30; font-size: 18px; padding: 0 8px; transition: opacity 0.2s; } .todo-delete:hover { opacity: 0.7; } .todo-add-form { padding: 20px 28px; border-top: 1px solid #f0f0f0; } .todo-input-group { margin-bottom: 12px; } .todo-input-label { font-size: 13px; color: #1d1d1f; margin-bottom: 8px; display: block; font-weight: 500; } .todo-input { width: 100%; padding: 10px 12px; background: #f5f5f7; border: 1px solid transparent; border-radius: 8px; color: #1d1d1f; font-size: 14px; transition: all 0.2s; box-sizing: border-box; } .todo-input:focus { outline: none; background-color: #ffffff; border: 1px solid #0071e3; box-shadow: 0 0 0 3px rgba(0, 113, 227, 0.1); } .todo-input-row { display: flex; gap: 12px; } .todo-input-row .todo-input-group { flex: 1; } .todo-select { width: 100%; padding: 10px 12px; background: #f5f5f7; border: 1px solid transparent; border-radius: 8px; color: #1d1d1f; font-size: 14px; transition: all 0.2s; cursor: pointer; } .todo-select:focus { outline: none; background-color: #ffffff; border: 1px solid #0071e3; box-shadow: 0 0 0 3px rgba(0, 113, 227, 0.1); } .todo-add-btn { cursor: pointer; padding: 12px; width: 100%; background: linear-gradient(180deg, #0071e3 0%, #005bb5 100%); font-size: 14px; color: #ffffff; border: 0; border-radius: 12px; font-weight: 600; transition: all 0.2s; box-shadow: 0 2px 8px rgba(0, 113, 227, 0.3); margin-top: 12px; } .todo-add-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0, 113, 227, 0.4); } .todo-add-btn:active { transform: translateY(0); } .todo-empty { text-align: center; padding: 40px 20px; color: #86868b; font-size: 14px; } /* 赞赏面板样式 */ .donate-panel { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: auto; background: transparent; box-shadow: none; border-radius: 0; z-index: 999999; overflow: visible; } .donate-header { display: none; } .donate-title { display: none; } .donate-close { position: absolute; top: -15px; right: -15px; cursor: pointer; font-size: 32px; color: #ff4d4f; line-height: 1; transition: all 0.2s; font-weight: bold; background: white; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); z-index: 10; } .donate-close:hover { color: #fff; background: #ff4d4f; transform: rotate(90deg); } .donate-body { padding: 0; text-align: center; background: transparent; } .donate-qrcode { width: auto; height: auto; margin: 0; display: block; background: transparent; border-radius: 0; } .donate-qrcode img { max-width: 100%; max-height: 100%; border-radius: 0; display: block; } .donate-message { margin-top: 20px; font-size: 16px; color: #1d1d1f; text-align: center; line-height: 1.6; font-weight: 500; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); } .donate-text { display: none; } .donate-tip { display: none; } .donate-upload { display: none; } .donate-upload input[type="file"] { display: none; } .donate-upload-label { display: none; } /* 配置面板样式 - 苹果风格简约设计 */ .live2d-config-panel { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 480px; max-height: 85vh; background: #FFFFFF; box-shadow: 0px 187px 75px rgba(0, 0, 0, 0.01), 0px 105px 63px rgba(0, 0, 0, 0.05), 0px 47px 47px rgba(0, 0, 0, 0.09), 0px 12px 26px rgba(0, 0, 0, 0.1); border-radius: 26px; z-index: 999999; overflow: hidden; } .live2d-config-header { display: flex; justify-content: space-between; align-items: center; padding: 24px 28px 20px; border-bottom: 1px solid #f0f0f0; } .live2d-config-title { font-size: 20px; color: #1d1d1f; font-weight: 600; letter-spacing: -0.3px; } .live2d-config-close { cursor: pointer; font-size: 28px; color: #86868b; line-height: 1; transition: color 0.2s; font-weight: 300; } .live2d-config-close:hover { color: #1d1d1f; } /* 标签页切换 */ .config-tabs { display: flex; position: relative; background-color: #f5f5f7; margin: 20px 28px; padding: 4px; border-radius: 12px; } .config-tabs input[type="radio"] { display: none; } .config-tab { display: flex; align-items: center; justify-content: center; flex: 1; height: 36px; font-size: 13px; color: #1d1d1f; font-weight: 500; border-radius: 10px; cursor: pointer; transition: color 0.2s ease; z-index: 2; } .config-tabs input[type="radio"]:checked + label { color: #1d1d1f; } .config-tabs input[id="tab-basic"]:checked ~ .tab-glider { transform: translateX(0); } .config-tabs input[id="tab-weather"]:checked ~ .tab-glider { transform: translateX(100%); } .config-tabs input[id="tab-health"]:checked ~ .tab-glider { transform: translateX(200%); } .config-tabs input[id="tab-messages"]:checked ~ .tab-glider { transform: translateX(300%); } .config-tabs input[id="tab-todo"]:checked ~ .tab-glider { transform: translateX(400%); } .config-tabs input[id="tab-donate"]:checked ~ .tab-glider { transform: translateX(500%); } .tab-glider { position: absolute; display: flex; height: 36px; width: calc(16.666% - 3px); background-color: #ffffff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); z-index: 1; border-radius: 10px; transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); } /* 待办徽章样式 */ .todo-badge { position: absolute; top: -8px; right: -8px; background: #ff4d4f; color: white; font-size: 10px; font-weight: 600; padding: 2px 6px; border-radius: 10px; min-width: 18px; height: 18px; line-height: 14px; text-align: center; box-shadow: 0 2px 4px rgba(255, 77, 79, 0.4); } .config-tab { position: relative; } .live2d-config-body { max-height: calc(85vh - 250px); overflow-y: auto; padding: 0 28px 20px; } .live2d-config-body::-webkit-scrollbar { width: 6px; } .live2d-config-body::-webkit-scrollbar-track { background: transparent; } .live2d-config-body::-webkit-scrollbar-thumb { background: #d1d1d6; border-radius: 3px; } .config-section { display: none; animation: fadeIn 0.3s ease; } .config-section.active { display: block; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .config-section-title { font-size: 15px; color: #1d1d1f; margin: 10px 0 8px; font-weight: 600; } .config-item { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid #f0f0f0; } .config-item:last-child { border-bottom: none; } .config-label { font-size: 14px; color: #1d1d1f; flex: 1; } .config-desc { font-size: 12px; color: #1d1d1f; margin-top: 4px; } .config-input-number { width: 70px; height: 36px; padding: 0 12px; background: #f5f5f7; border: 1px solid transparent; border-radius: 8px; color: #1d1d1f; font-size: 14px; text-align: center; transition: all 0.2s; } .config-input-number:focus { outline: none; background-color: #ffffff; border: 1px solid #0071e3; box-shadow: 0 0 0 3px rgba(0, 113, 227, 0.1); } /* 开关按钮 - 苹果风格 */ .switch { position: relative; width: 51px; height: 31px; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #e5e5ea; transition: 0.3s; border-radius: 31px; } .slider:before { position: absolute; content: ""; height: 27px; width: 27px; left: 2px; bottom: 2px; background-color: white; transition: 0.3s; border-radius: 50%; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } .switch input:checked + .slider { background-color: #34c759; } .switch input:checked + .slider:before { transform: translateX(20px); } /* 保存按钮 */ .config-button { cursor: pointer; padding: 14px; width: calc(100% - 56px); margin: 20px 28px; background: linear-gradient(180deg, #0071e3 0%, #005bb5 100%); font-size: 14px; color: #ffffff; border: 0; border-radius: 12px; font-weight: 600; transition: all 0.2s; box-shadow: 0 2px 8px rgba(0, 113, 227, 0.3); } .config-button:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0, 113, 227, 0.4); } .config-button:active { transform: translateY(0); } /* 台词编辑区域 */ .message-editor { margin-top: 4px; } .message-editor textarea { width: 100%; min-height: 90px; padding: 12px; background: #f5f5f7; border: 1px solid transparent; border-radius: 10px; color: #1d1d1f; font-size: 13px; resize: vertical; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.5; transition: all 0.2s; } .message-editor textarea:focus { outline: none; background-color: #ffffff; border: 1px solid #0071e3; box-shadow: 0 0 0 3px rgba(0, 113, 227, 0.1); } .message-editor-label { font-size: 13px; color: #1d1d1f; margin-bottom: 8px; display: block; font-weight: 500; } .message-editor-hint { font-size: 11px; color: #86868b; margin-top: 6px; } /* 恢复默认按钮 */ .reset-button { cursor: pointer; padding: 6px 14px; background: transparent; border: 1px solid #d1d1d6; border-radius: 8px; font-size: 12px; color: #86868b; transition: all 0.2s; font-weight: 500; } .reset-button:hover { background: #f5f5f7; border-color: #86868b; color: #1d1d1f; } .reset-button:active { transform: scale(0.98); } .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; } `); // ========== 更新待办徽章 ========== function updateTodoBadge() { const count = config.todos.list ? config.todos.list.length : 0; const badge = $('.todo-badge'); if (count > 0) { if (badge.length === 0) { $('label[for="tab-todo"]').append(`${count}`); } else { badge.text(count); } } else { badge.remove(); } } // ========== 渲染配置面板中的待办列表 ========== function renderConfigTodoList() { const todoList = $('#configTodoList'); todoList.empty(); if (!config.todos.list || config.todos.list.length === 0) { todoList.html('
暂无待办事项
点击下方添加吧~
'); return; } config.todos.list.forEach((todo, index) => { const timeText = todo.type === 'time' ? ` ${todo.time}` : ` 每${todo.interval}分钟`; const todoHtml = `
${todo.text}
${timeText}
×
`; todoList.append(todoHtml); }); // 绑定完成事件 $('#configTodoList .todo-checkbox').click((e) => { const index = $(e.target).data('index'); if (todoSystem) { todoSystem.completeTodo(index); setTimeout(() => { renderConfigTodoList(); updateTodoBadge(); }, 1000); } }); // 绑定删除事件 $('#configTodoList .todo-delete').click((e) => { const index = $(e.target).data('index'); if (todoSystem) { todoSystem.deleteTodo(index); renderConfigTodoList(); updateTodoBadge(); } }); } // ========== 配置面板 ========== function showConfigPanel() { const panel = `
看板娘设置
×
基础设置
你的昵称
看板娘会用这个昵称称呼你哦~
启用黑名单
在黑名单中的网站不会显示看板娘
每行一个域名或关键词
当前网站:${window.location.hostname}
天气设置
设置你所在的省份和城市,点击天气图标即可查看天气信息
省份
不用加"省",如:广东、浙江、北京
城市
不用加"市",如:广州、深圳、杭州
想要定制其他功能,可以联系 yyy.
测试天气功能
健康提醒
启用健康提醒
提醒时间段:${config.healthReminders.workingHours.start}:00 - ${config.healthReminders.workingHours.end}:00
喝水提醒
启用
间隔(分钟)
休息提醒
启用
间隔(分钟)
坐姿提醒
启用
间隔(分钟)
睡觉提醒
启用
提醒时间(小时)
自定义提醒台词
每行一句台词,随机显示。使用 {nickname} 可显示昵称
自定义台词
每行一句台词,随机显示。使用 {nickname} 可显示昵称
1分钟无操作后有30%概率触发
我的待办
爱与正义的化身
爱与正义的化身
君子爱财取之有道
爱与正义
可以请 yyy. 喝杯 coffee 吗?
我都那么了喵喵喵~
`; $('#live2dConfigPanel').remove(); $('body').append(panel); $('#live2dConfigPanel').fadeIn(300); // 更新待办徽章 updateTodoBadge(); // 标签页切换 $('input[name="tabs"]').change(function() { const tabId = $(this).attr('id').replace('tab-', ''); $('.config-section').removeClass('active'); $(`.config-section[data-tab="${tabId}"]`).addClass('active'); // 如果切换到待办标签,渲染待办列表 if (tabId === 'todo') { if (todoSystem) { renderConfigTodoList(); } } // 如果切换到待办或赞赏标签,隐藏保存按钮 if (tabId === 'todo' || tabId === 'donate') { $('#live2dConfigSave').hide(); } else { $('#live2dConfigSave').show(); } }); // 待办类型切换 $('#configTodoType').change(function() { if ($(this).val() === 'time') { $('#configTodoTimeGroup').show(); $('#configTodoIntervalGroup').hide(); } else { $('#configTodoTimeGroup').hide(); $('#configTodoIntervalGroup').show(); } }); // 添加待办 $('#configTodoAddBtn').click(function() { const text = $('#configTodoText').val().trim(); const type = $('#configTodoType').val(); if (!text) { if (typeof showMessage === 'function') { showMessage('请输入待办事项', 2000); } return; } let value; if (type === 'time') { value = $('#configTodoTime').val(); if (!value) { if (typeof showMessage === 'function') { showMessage('请选择提醒时间', 2000); } return; } } else { value = parseInt($('#configTodoInterval').val()); if (!value || value < 1) { if (typeof showMessage === 'function') { showMessage('请输入有效的间隔时间', 2000); } return; } } if (todoSystem) { todoSystem.addTodo(text, type, value); $('#configTodoText').val(''); $('#configTodoTime').val(''); $('#configTodoInterval').val('30'); renderConfigTodoList(); updateTodoBadge(); } }); // 测试天气功能 $('#testWeather').click(function() { // 临时更新配置 config.weather.province = $('#cfg_province').val().trim() || '广东'; config.weather.city = $('#cfg_city').val().trim() || '广州'; // 关闭面板 $('#live2dConfigPanel').fadeOut(300); // 延迟一下再调用天气功能 setTimeout(() => { getWeather(); }, 500); }); // 添加当前网站到黑名单 $('#addCurrentSite').click(function() { const currentHost = window.location.hostname; const blacklistText = $('#cfg_blacklist').val(); const blacklistArray = blacklistText.split('\n').filter(line => line.trim()); if (!blacklistArray.includes(currentHost)) { blacklistArray.push(currentHost); $('#cfg_blacklist').val(blacklistArray.join('\n')); if (typeof showMessage === 'function') { showMessage('已添加当前网站到黑名单', 2000); } } else { if (typeof showMessage === 'function') { showMessage('当前网站已在黑名单中', 2000); } } }); // 绑定关闭事件 $('#live2dConfigClose').click(function() { $('#live2dConfigPanel').fadeOut(300); }); // 绑定恢复默认按钮事件 $('.reset-button').click(function() { const resetType = $(this).data('reset'); switch(resetType) { case 'basic': // 恢复基础设置 $('#cfg_nickname').val(DEFAULT_CONFIG.nickname); $('#cfg_useBlacklist').prop('checked', DEFAULT_CONFIG.useBlacklist); $('#cfg_blacklist').val(DEFAULT_CONFIG.blacklist.join('\n')); if (typeof showMessage === 'function') { showMessage('基础设置已恢复默认', 2000); } break; case 'weather': // 恢复天气设置 $('#cfg_province').val(DEFAULT_CONFIG.weather.province); $('#cfg_city').val(DEFAULT_CONFIG.weather.city); if (typeof showMessage === 'function') { showMessage('天气设置已恢复默认', 2000); } break; case 'health': // 恢复健康提醒设置 $('#cfg_healthEnabled').prop('checked', DEFAULT_CONFIG.healthReminders.enabled); $('#cfg_waterEnabled').prop('checked', DEFAULT_CONFIG.healthReminders.water.enabled); $('#cfg_waterInterval').val(DEFAULT_CONFIG.healthReminders.water.interval); $('#cfg_waterMessages').val(DEFAULT_CONFIG.healthReminders.water.messages.join('\n')); $('#cfg_restEnabled').prop('checked', DEFAULT_CONFIG.healthReminders.rest.enabled); $('#cfg_restInterval').val(DEFAULT_CONFIG.healthReminders.rest.interval); $('#cfg_restMessages').val(DEFAULT_CONFIG.healthReminders.rest.messages.join('\n')); $('#cfg_postureEnabled').prop('checked', DEFAULT_CONFIG.healthReminders.posture.enabled); $('#cfg_postureInterval').val(DEFAULT_CONFIG.healthReminders.posture.interval); $('#cfg_postureMessages').val(DEFAULT_CONFIG.healthReminders.posture.messages.join('\n')); $('#cfg_sleepEnabled').prop('checked', DEFAULT_CONFIG.healthReminders.sleep.enabled); $('#cfg_sleepTime').val(DEFAULT_CONFIG.healthReminders.sleep.time); $('#cfg_sleepMessages').val(DEFAULT_CONFIG.healthReminders.sleep.messages.join('\n')); if (typeof showMessage === 'function') { showMessage('健康提醒设置已恢复默认', 2000); } break; case 'messages': // 恢复自定义台词 $('#cfg_clickMessages').val(DEFAULT_CONFIG.customMessages.click.join('\n')); $('#cfg_idleMessages').val(DEFAULT_CONFIG.customMessages.idle.join('\n')); $('#cfg_morningMessages').val(DEFAULT_CONFIG.customMessages.welcome.morning.join('\n')); $('#cfg_noonMessages').val(DEFAULT_CONFIG.customMessages.welcome.noon.join('\n')); $('#cfg_afternoonMessages').val(DEFAULT_CONFIG.customMessages.welcome.afternoon.join('\n')); $('#cfg_eveningMessages').val(DEFAULT_CONFIG.customMessages.welcome.evening.join('\n')); $('#cfg_nightMessages').val(DEFAULT_CONFIG.customMessages.welcome.night.join('\n')); if (typeof showMessage === 'function') { showMessage('自定义台词已恢复默认', 2000); } break; } }); // 绑定保存事件 - 实时生效 $('#live2dConfigSave').click(function() { // 保存基础配置 config.nickname = $('#cfg_nickname').val().trim() || '宝宝'; config.useBlacklist = $('#cfg_useBlacklist').is(':checked'); config.blacklist = $('#cfg_blacklist').val().split('\n').filter(line => line.trim()); config.weather.province = $('#cfg_province').val().trim() || '广东'; config.weather.city = $('#cfg_city').val().trim() || '广州'; // 保存健康提醒配置 const oldHealthEnabled = config.healthReminders.enabled; config.healthReminders.enabled = $('#cfg_healthEnabled').is(':checked'); config.healthReminders.water.enabled = $('#cfg_waterEnabled').is(':checked'); config.healthReminders.water.interval = parseInt($('#cfg_waterInterval').val()); config.healthReminders.water.messages = $('#cfg_waterMessages').val().split('\n').filter(line => line.trim()); config.healthReminders.rest.enabled = $('#cfg_restEnabled').is(':checked'); config.healthReminders.rest.interval = parseInt($('#cfg_restInterval').val()); config.healthReminders.rest.messages = $('#cfg_restMessages').val().split('\n').filter(line => line.trim()); config.healthReminders.posture.enabled = $('#cfg_postureEnabled').is(':checked'); config.healthReminders.posture.interval = parseInt($('#cfg_postureInterval').val()); config.healthReminders.posture.messages = $('#cfg_postureMessages').val().split('\n').filter(line => line.trim()); config.healthReminders.sleep.enabled = $('#cfg_sleepEnabled').is(':checked'); config.healthReminders.sleep.time = parseInt($('#cfg_sleepTime').val()); config.healthReminders.sleep.messages = $('#cfg_sleepMessages').val().split('\n').filter(line => line.trim()); // 保存自定义台词 config.customMessages.click = $('#cfg_clickMessages').val().split('\n').filter(line => line.trim()); config.customMessages.idle = $('#cfg_idleMessages').val().split('\n').filter(line => line.trim()); config.customMessages.welcome.morning = $('#cfg_morningMessages').val().split('\n').filter(line => line.trim()); config.customMessages.welcome.noon = $('#cfg_noonMessages').val().split('\n').filter(line => line.trim()); config.customMessages.welcome.afternoon = $('#cfg_afternoonMessages').val().split('\n').filter(line => line.trim()); config.customMessages.welcome.evening = $('#cfg_eveningMessages').val().split('\n').filter(line => line.trim()); config.customMessages.welcome.night = $('#cfg_nightMessages').val().split('\n').filter(line => line.trim()); saveConfig(); // 实时更新健康提醒系统 if (reminderSystem) { reminderSystem.stop(); } if (config.healthReminders.enabled) { reminderSystem = new HealthReminderSystem(); reminderSystem.init(); } $('#live2dConfigPanel').fadeOut(300); if (typeof showMessage === 'function') { showMessage('设置已保存!黑名单修改需刷新页面生效', 4000, true); } }); } // ========== 待办提醒系统 ========== class TodoReminderSystem { constructor() { this.timers = {}; this.lastReminders = {}; } init() { if (!config.todos.enabled) { console.log('[Live2D] 待办提醒已禁用'); return; } console.log('[Live2D] 待办提醒系统启动'); this.loadTodos(); this.startAllReminders(); } loadTodos() { this.renderTodoList(); } renderTodoList() { const todoList = $('#todoList'); todoList.empty(); if (!config.todos.list || config.todos.list.length === 0) { todoList.html('
暂无待办事项
点击下方添加吧~
'); return; } config.todos.list.forEach((todo, index) => { const timeText = todo.type === 'time' ? ` ${todo.time}` : ` 每${todo.interval}分钟`; const todoHtml = `
${todo.text}
${timeText}
×
`; todoList.append(todoHtml); }); // 绑定完成事件 $('.todo-checkbox').click((e) => { const index = $(e.target).data('index'); this.completeTodo(index); }); // 绑定删除事件 $('.todo-delete').click((e) => { const index = $(e.target).data('index'); this.deleteTodo(index); }); } addTodo(text, type, value) { const nickname = config.nickname || '宝宝'; const todo = { id: Date.now(), text: text, type: type, [type === 'time' ? 'time' : 'interval']: value, lastReminded: null }; config.todos.list.push(todo); saveConfig(); this.renderTodoList(); this.startReminder(todo); updateTodoBadge(); // 更新徽章 if (typeof showMessage === 'function') { showMessage(`待办已添加!${nickname}我会按时提醒你的~`, 3000, true); } } deleteTodo(index) { const todo = config.todos.list[index]; if (this.timers[todo.id]) { clearInterval(this.timers[todo.id]); delete this.timers[todo.id]; } config.todos.list.splice(index, 1); saveConfig(); this.renderTodoList(); updateTodoBadge(); // 更新徽章 if (typeof showMessage === 'function') { showMessage('待办已删除', 2000); } } completeTodo(index) { const todo = config.todos.list[index]; const nickname = config.nickname || '宝宝'; if (typeof showMessage === 'function') { showMessage(`太棒了${nickname}!又完成一件事~`, 3000, true); } setTimeout(() => { this.deleteTodo(index); }, 1000); } startAllReminders() { config.todos.list.forEach(todo => { this.startReminder(todo); }); } startReminder(todo) { if (todo.type === 'time') { // 指定时间提醒 this.timers[todo.id] = setInterval(() => { this.checkTimeReminder(todo); }, 60000); // 每分钟检查一次 this.checkTimeReminder(todo); // 立即检查一次 } else { // 间隔提醒 const intervalMs = todo.interval * 60 * 1000; this.timers[todo.id] = setInterval(() => { this.showTodoReminder(todo); }, intervalMs); } } checkTimeReminder(todo) { const now = new Date(); const currentTime = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; if (currentTime === todo.time) { const today = now.toDateString(); if (todo.lastReminded !== today) { this.showTodoReminder(todo); todo.lastReminded = today; saveConfig(); } } } showTodoReminder(todo) { const nickname = config.nickname || '宝宝'; if (typeof showMessage === 'function') { showMessage(` ${nickname}待办提醒:${todo.text}`, 6000, true); } } stop() { Object.keys(this.timers).forEach(key => { clearInterval(this.timers[key]); }); this.timers = {}; console.log('[Live2D] 待办提醒系统已停止'); } } // ========== 健康提醒系统 ========== class HealthReminderSystem { constructor() { this.timers = {}; this.startTime = Date.now(); this.lastReminders = {}; } init() { if (!config.healthReminders.enabled) { console.log('[Live2D] 健康提醒已禁用'); return; } console.log('[Live2D] 健康提醒系统启动'); if (config.healthReminders.water.enabled) { this.startReminder('water', config.healthReminders.water.interval); } if (config.healthReminders.rest.enabled) { this.startReminder('rest', config.healthReminders.rest.interval); } if (config.healthReminders.posture.enabled) { this.startReminder('posture', config.healthReminders.posture.interval); } if (config.healthReminders.sleep.enabled) { this.checkSleepTime(); this.timers.sleep = setInterval(() => this.checkSleepTime(), 60000); } } startReminder(type, intervalMinutes) { const intervalMs = intervalMinutes * 60 * 1000; this.timers[type] = setInterval(() => { if (this.shouldShowReminder()) { this.showReminder(type); } }, intervalMs); console.log(`[Live2D] ${type} 提醒已启动,间隔 ${intervalMinutes} 分钟`); } shouldShowReminder() { const now = new Date(); const hour = now.getHours(); const { start, end } = config.healthReminders.workingHours; return hour >= start && hour < end; } showReminder(type) { let messages; switch(type) { case 'water': messages = config.healthReminders.water.messages; break; case 'rest': messages = config.healthReminders.rest.messages; break; case 'posture': messages = config.healthReminders.posture.messages; break; default: return; } const message = messages[Math.floor(Math.random() * messages.length)]; this.displayMessage(message); } checkSleepTime() { const now = new Date(); const hour = now.getHours(); const minute = now.getMinutes(); if (hour === config.healthReminders.sleep.time && minute === 0) { const today = now.toDateString(); if (this.lastReminders.sleep !== today) { const messages = config.healthReminders.sleep.messages; const message = messages[Math.floor(Math.random() * messages.length)]; this.displayMessage(message); this.lastReminders.sleep = today; } } } displayMessage(text) { const nickname = config.nickname || '宝宝'; const message = text.replace(/\{nickname\}/g, nickname); if (typeof showMessage === 'function') { showMessage(message, 5000, true); } else { console.log('[Live2D] 提醒:', message); } } stop() { Object.keys(this.timers).forEach(key => { clearInterval(this.timers[key]); }); this.timers = {}; console.log('[Live2D] 健康提醒系统已停止'); } } // ========== 城市代码数据(完整版本) ========== const CITY_CODES_DATA = [ ]; // 查找城市代码的函数(和脚本猫完全一样的逻辑) function findCityCode(province, city) { let provinceData = null; // 先找省份 if (province) { const cleanProvince = province.replace(/[省市]$/,""); provinceData = CITY_CODES_DATA.find(item => item.city_name.indexOf(cleanProvince) !== -1 && item.pid === 0); } if (!provinceData) { console.log(`您当前填写的province: 【${province}】, 找不到该城市或省份,城市天气可能会因此不准确。请知悉`); } // 再找城市 if (city) { const cleanCity = city.replace(/[市区县]$/,""); // 尝试不同的后缀:县|区|市|(空字符串) for (const suffix of "县|区|市|".split("|")) { const cityData = CITY_CODES_DATA.find(item => { if (provinceData) { return item.pid === provinceData.id && item.city_name === `${cleanCity}${suffix}`; } else { return item.city_name === `${cleanCity}${suffix}`; } }); if (cityData) { return cityData; } } } // 如果找不到城市,返回省份数据 return provinceData && provinceData.city_code ? provinceData : null; } // ========== Live2D 模型切换功能 ========== function loadOtherModel() { const modelId = localStorage.getItem('modelId') || live2d_settings.modelId; const modelRandMode = 'switch'; // 可选 'rand'(随机) 或 'switch'(顺序) 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 || '新的看板娘来啦~'; // 不保存到配置,使用原始localStorage方式 if (typeof loadModel === 'function') { loadModel(newModelId, 0); } else if (typeof loadlive2d === 'function') { localStorage.setItem('modelId', newModelId); localStorage.setItem('modelTexturesId', 0); loadlive2d('live2d', `https://live2d.fghrsh.net/api/get/?id=${newModelId}-0`); } if (typeof showMessage === 'function') { showMessage(message, 3000, true); } } }, error: function() { if (typeof showMessage === 'function') { showMessage('切换失败了...', 3000); } } }); } function loadRandTextures() { const modelId = localStorage.getItem('modelId') || live2d_settings.modelId; const modelTexturesId = localStorage.getItem('modelTexturesId') || 0; const modelTexturesRandMode = 'rand'; // 可选 'rand'(随机) 或 'switch'(顺序) 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; if (newTexturesId === 1 && (modelTexturesId === 1 || modelTexturesId === 0)) { if (typeof showMessage === 'function') { showMessage('我还没有其他衣服呢', 3000, true); } } else { if (typeof showMessage === 'function') { showMessage('我的新衣服好看嘛', 3000, true); } } // 不保存到配置,使用原始localStorage方式 if (typeof loadModel === 'function') { loadModel(modelId, newTexturesId); } else if (typeof loadlive2d === 'function') { localStorage.setItem('modelTexturesId', newTexturesId); loadlive2d('live2d', `https://live2d.fghrsh.net/api/get/?id=${modelId}-${newTexturesId}`); } } }, error: function() { if (typeof showMessage === 'function') { showMessage('换装失败了...', 3000); } } }); } // ========== 天气功能 ========== function getWeather() { const province = config.weather.province; const city = config.weather.city; messageSystem.showImportant('正在获取天气信息...', 2000); console.log('[Live2D] 开始获取天气,省份:', province, '城市:', city); // 获取城市数据 const cityData = findCityCode(province, city); console.log('[Live2D] 查找到的城市数据:', cityData); if (!cityData || !cityData.city_code) { console.error('[Live2D] 找不到城市代码'); messageSystem.showImportant(`抱歉,暂不支持${city}的天气查询哦~\n请在设置中检查省份和城市配置`, 4000); return; } // 使用和脚本猫完全一样的天气API const apiUrl = `http://t.weather.itboy.net/api/weather/city/${cityData.city_code}`; console.log('[Live2D] 天气API地址:', apiUrl); // 使用 GM_xmlhttpRequest 绕过 CORS 和混合内容限制 GM_xmlhttpRequest({ url: apiUrl, method: 'GET', headers: { 'Content-Type': 'application/json' }, timeout: 10000, onload: function(response) { try { const data = JSON.parse(response.responseText); console.log('[Live2D] 天气API响应:', data); if (data && data.status === 200 && data.data) { const weatherData = data.data; const forecast = weatherData.forecast && weatherData.forecast[0]; if (!forecast) { console.error('[Live2D] 天气数据格式错误'); messageSystem.showImportant('天气信息获取失败,请稍后再试', 3000); return; } // 分段显示天气信息 const messages = [ `今日${city}天气:${forecast.type}\n当前温度:${weatherData.wendu}°C 湿度:${weatherData.shidu}`, `${forecast.high.replace('高温 ', '最高温度:')}\n${forecast.low.replace('低温 ', '最低温度:')}\n${forecast.fx} ${forecast.fl}`, `空气质量:${weatherData.quality}(PM2.5: ${weatherData.pm25})\n${forecast.notice}`, `健康提示:${weatherData.ganmao}` ]; // 依次显示每段信息 messages.forEach((msg, index) => { setTimeout(() => { messageSystem.showImportant(msg, 5000); }, index * 5500); }); } else { console.error('[Live2D] 天气API返回状态异常:', data); messageSystem.showImportant('天气信息获取失败,请检查城市设置', 3000); } } catch (e) { console.error('[Live2D] 解析天气数据失败:', e); messageSystem.showImportant('天气信息解析失败', 3000); } }, onerror: function(error) { console.error('[Live2D] 天气API请求失败:', error); messageSystem.showImportant('天气信息获取失败,请稍后再试', 3000); }, ontimeout: function() { console.error('[Live2D] 天气API请求超时'); messageSystem.showImportant('天气信息获取超时,请稍后再试', 3000); } }); } // ========== 自定义消息 ========== function showCustomWelcome() { const now = new Date(); const hour = now.getHours(); const nickname = config.nickname || '宝宝'; let messages; if (hour >= 5 && hour < 11) { messages = config.customMessages.welcome.morning.map(msg => msg.replace(/\{nickname\}/g, nickname)); } else if (hour >= 11 && hour < 13) { messages = config.customMessages.welcome.noon.map(msg => msg.replace(/\{nickname\}/g, nickname)); } else if (hour >= 13 && hour < 18) { messages = config.customMessages.welcome.afternoon.map(msg => msg.replace(/\{nickname\}/g, nickname)); } else if (hour >= 18 && hour < 22) { messages = config.customMessages.welcome.evening.map(msg => msg.replace(/\{nickname\}/g, nickname)); } else { messages = config.customMessages.welcome.night.map(msg => msg.replace(/\{nickname\}/g, nickname)); } const message = messages[Math.floor(Math.random() * messages.length)]; setTimeout(() => { if (typeof showMessage === 'function') { showMessage(message, 6000, true); } }, 3000); } function setupCustomMessages() { $(document).on('click', '.waifu #live2d', function() { const nickname = config.nickname || '宝宝'; const messages = config.customMessages.click; let message = messages[Math.floor(Math.random() * messages.length)]; message = message.replace(/\{nickname\}/g, nickname); if (typeof showMessage === 'function') { showMessage(message, 3000, true); } }); let idleTimer; $(document).on('mousemove keydown', function() { clearTimeout(idleTimer); idleTimer = setTimeout(() => { if (Math.random() < 0.3) { const nickname = config.nickname || '宝宝'; const messages = config.customMessages.idle; let message = messages[Math.floor(Math.random() * messages.length)]; message = message.replace(/\{nickname\}/g, nickname); if (typeof showMessage === 'function') { showMessage(message, 4000); } } }, 60000); }); // 缩小看板娘的触发区域,排除工具栏 setTimeout(() => { // 移除原有的看板娘 mouseover 事件 $(document).off('mouseover', '.waifu #live2d'); // 只在 canvas 上添加 mouseover 事件,并检查鼠标位置 $(document).on('mouseover', '.waifu #live2d', function(e) { const canvas = this; const rect = canvas.getBoundingClientRect(); const mouseX = e.clientX - rect.left; // 如果鼠标在右侧 60px 区域(工具栏区域),不触发 if (mouseX > rect.width - 60) { return; } // 只有在没有重要消息时才显示 if (!messageSystem.isImportantMessageShowing) { const texts = ["干嘛呢你,快把手拿开", "鼠…鼠标放错地方了!"]; const text = texts[Math.floor(Math.random() * texts.length)]; if (typeof showMessage === 'function') { showMessage(text, 3000); } } }); }, 1000); } // ========== 拖拽停靠功能(修复版) ========== function initDragDocking() { setTimeout(() => { const $waifu = $('.waifu'); const $tool = $('.waifu-tool'); if (!$waifu.length) return; let isDragging = false; let startX = 0; let startWaifuLeft = 0; // 监听鼠标按下(开始拖拽) $waifu.on('mousedown', function(e) { const rect = this.getBoundingClientRect(); const mouseX = e.clientX - rect.left; // 排除工具栏区域(右侧60px),仅本体可拖拽 if (mouseX > rect.width - 60) return; isDragging = true; startX = e.clientX; // 获取看板娘当前的实际位置 const rect2 = this.getBoundingClientRect(); startWaifuLeft = rect2.left; // 拖拽开始时立即切换到left定位,强制清除right定位 $waifu[0].style.setProperty('right', 'auto', 'important'); $waifu[0].style.setProperty('left', startWaifuLeft + 'px', 'important'); $waifu[0].style.setProperty('transition', 'none', 'important'); console.log('开始拖拽,初始位置:', startWaifuLeft); e.preventDefault(); }); // 监听鼠标移动(拖拽过程,实时更新位置) $(document).on('mousemove', function(e) { if (!isDragging) return; const moveX = e.clientX - startX; let newLeft = startWaifuLeft + moveX; const currentPageWidth = $(window).width(); const currentWaifuWidth = $waifu.outerWidth(); newLeft = Math.max(0, Math.min(newLeft, currentPageWidth - currentWaifuWidth)); $waifu[0].style.setProperty('left', newLeft + 'px', 'important'); $waifu[0].style.setProperty('right', 'auto', 'important'); }); // 监听鼠标松开(结束拖拽,执行平滑停靠动画) $(document).on('mouseup', function() { if (!isDragging) return; isDragging = false; const currentLeft = parseFloat($waifu.css('left')); const currentPageWidth = $(window).width(); const currentWaifuWidth = $waifu.outerWidth(); console.log('拖拽结束,当前位置:', currentLeft, '页面宽度:', currentPageWidth, '看板娘宽度:', currentWaifuWidth); if (currentLeft + currentWaifuWidth/2 < currentPageWidth/2) { // 停靠左侧 - 使用平滑动画 console.log('判定停靠到左边,目标位置: 0'); const startLeft = currentLeft; const targetLeft = 0; const duration = 1000; const startTime = performance.now(); function animateToLeft(currentTime) { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); // 使用缓动函数 const easeProgress = 1 - Math.pow(1 - progress, 3); const newLeft = startLeft + (targetLeft - startLeft) * easeProgress; // 强制设置位置 $waifu[0].style.setProperty('left', newLeft + 'px', 'important'); $waifu[0].style.setProperty('right', 'auto', 'important'); if (progress < 1) { requestAnimationFrame(animateToLeft); } else { // 动画完成,最终确保位置正确 $waifu[0].style.setProperty('left', '0px', 'important'); $waifu[0].style.setProperty('right', 'auto', 'important'); console.log('左侧停靠完成,最终位置:', $waifu.css('left')); } } requestAnimationFrame(animateToLeft); $tool.addClass('left-side').css({ left: '10px', right: 'auto' }); // 保存停靠位置到配置 config.dockPosition = 'left'; saveConfig(); } else { // 停靠右侧 - 使用平滑动画 console.log('判定停靠到右边'); const startLeft = currentLeft; const targetLeft = currentPageWidth - currentWaifuWidth; const duration = 1000; const startTime = performance.now(); function animateToRight(currentTime) { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); // 使用缓动函数 const easeProgress = 1 - Math.pow(1 - progress, 3); const newLeft = startLeft + (targetLeft - startLeft) * easeProgress; // 强制设置位置 $waifu[0].style.setProperty('left', newLeft + 'px', 'important'); $waifu[0].style.setProperty('right', 'auto', 'important'); if (progress < 1) { requestAnimationFrame(animateToRight); } else { // 动画完成后,改用right定位 $waifu[0].style.setProperty('left', 'auto', 'important'); $waifu[0].style.setProperty('right', '0px', 'important'); console.log('右侧停靠完成,最终位置:', $waifu.css('right')); } } requestAnimationFrame(animateToRight); $tool.removeClass('left-side').css({ right: '10px', left: 'auto' }); // 保存停靠位置到配置 config.dockPosition = 'right'; saveConfig(); } }); // 鼠标离开页面时强制结束拖拽,避免卡死 $(document).on('mouseleave', function() { if (isDragging) { isDragging = false; } }); }, 2000); } // ========== 初始化 ========== function init() { console.log('[Live2D] 开始初始化...'); // 加载动画 SVG const loadingSvg = config.showLoadingTip ? `
加载中...
` : ''; const waifuHtml = `
${loadingSvg}
`; // 检查是否已经存在看板娘,避免重复创建 if ($('.waifu').length > 0) { console.log('[Live2D] 看板娘已存在,跳过创建'); return; } $('body').append(waifuHtml); 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] 配置参数...'); live2d_settings['waifuEdgeSide'] = config.dockPosition === 'left' ? 'left:0' : 'right:0'; live2d_settings['waifuDraggable'] = 'disable'; live2d_settings['modelStorage'] = true; console.log('[Live2D] 调用 initModel...'); initModel('https://cdn.jsdelivr.net/gh/fghrsh/live2d_demo@master/assets/waifu-tips.json'); // 强制覆盖对话框样式,使其自动调整大小 setTimeout(() => { $('.waifu-tips').css({ 'width': 'fit-content', 'height': 'auto', 'min-width': '80px', 'max-width': '250px' }); }, 100); setTimeout(() => { $('.waifu-loading').fadeOut(300, function() { $(this).remove(); }); $('.waifu').addClass('loaded'); console.log('[Live2D] 看板娘加载完成!'); // 根据配置设置初始停靠位置 setTimeout(() => { const $waifu = $('.waifu'); const $tool = $('.waifu-tool'); if (config.dockPosition === 'left') { // 设置到左边 $waifu[0].style.setProperty('left', '0px', 'important'); $waifu[0].style.setProperty('right', 'auto', 'important'); $tool.addClass('left-side').css({ left: '10px', right: 'auto' }); console.log('[Live2D] 初始位置设置为左侧'); } else { // 设置到右边(默认) $waifu[0].style.setProperty('left', 'auto', 'important'); $waifu[0].style.setProperty('right', '0px', 'important'); $tool.removeClass('left-side').css({ right: '10px', left: 'auto' }); console.log('[Live2D] 初始位置设置为右侧'); } }, 500); showCustomWelcome(); setupCustomMessages(); // ========== 工具栏按钮事件绑定 ========== // 主页面按钮 $('.waifu-tool .el-icon-house').hover(function() { const nickname = config.nickname || '宝宝'; messageSystem.showNormal(`点击前往首页,${nickname}想回到上一页可以使用浏览器的后退功能哦`, 3000); }); $('.waifu-tool .el-icon-house').click(function() { window.location.href = window.location.origin; }); // 一言按钮 $('.waifu-tool .el-icon-chat-dot-round').hover(function() { messageSystem.showNormal('一言一语,一颦一笑。一字一句,一颗赛艇。', 3000); }); $('.waifu-tool .el-icon-chat-dot-round').click(function() { messageSystem.showImportant('正在获取一言...', 2000); $.ajax({ url: 'https://v.api.aa1.cn/api/yiyan/index.php', type: 'GET', dataType: 'text', timeout: 5000, success: function(data) { if (data) { messageSystem.showImportant(data, 8000); } }, error: function() { messageSystem.showImportant('获取一言失败了...', 3000); } }); }); // 天气按钮 $('.waifu-tool .el-icon-sunny').hover(function() { const nickname = config.nickname || '宝宝'; messageSystem.showNormal(`要看看今天天气怎么样吗?${nickname}`, 3000); }); $('.waifu-tool .el-icon-sunny').click(function() { getWeather(); }); // 切换看板娘按钮(切换模型) $('.waifu-tool .el-icon-user').hover(function() { const nickname = config.nickname || '宝宝'; messageSystem.showNormal(`嗯··· ${nickname}要切换看板娘吗?`, 3000); }); $('.waifu-tool .el-icon-user').click(function() { loadOtherModel(); }); // 换装按钮(切换材质) $('.waifu-tool .el-icon-magic-stick').hover(function() { const nickname = config.nickname || '宝宝'; messageSystem.showNormal(`${nickname}喜欢换装 Play 吗?`, 3000); }); $('.waifu-tool .el-icon-magic-stick').click(function() { loadRandTextures(); }); // 拍照按钮 $('.waifu-tool .el-icon-camera').hover(function() { const nickname = config.nickname || '宝宝'; messageSystem.showNormal(`${nickname}要拍张纪念照片吗?`, 3000); }); $('.waifu-tool .el-icon-camera').click(function() { if (typeof showMessage === 'function') { showMessage('照好了嘛,是不是很可爱呢?', 5000, true); } if (typeof window.Live2D !== 'undefined') { window.Live2D.captureName = 'live2d.png'; window.Live2D.captureFrame = true; } }); // 待办按钮 $('.waifu-tool .el-icon-document-checked').hover(function() { const nickname = config.nickname || '宝宝'; messageSystem.showNormal(`${nickname}来查看待办事项吧~`, 3000); }); $('.waifu-tool .el-icon-document-checked').click(function() { // 打开设置面板并切换到待办标签页 showConfigPanel(); setTimeout(() => { $('#tab-todo').prop('checked', true).trigger('change'); }, 100); }); // 设置按钮 $('.waifu-tool .el-icon-setting').hover(function() { const nickname = config.nickname || '宝宝'; messageSystem.showNormal(`请尽情吩咐小娘子,${nickname}`, 3000); }); $('.waifu-tool .el-icon-setting').click(function() { showConfigPanel(); }); // 赞赏按钮 $('.waifu-tool .el-icon-coffee-cup').hover(function() { const nickname = config.nickname || '宝宝'; messageSystem.showNormal(`${nickname}请我喝杯咖啡吧~`, 3000); }); $('.waifu-tool .el-icon-coffee-cup').click(function() { // 打开设置面板并切换到赞赏标签页 showConfigPanel(); setTimeout(() => { $('#tab-donate').prop('checked', true).trigger('change'); }, 100); }); // 关闭按钮 $('.waifu-tool .el-icon-switch-button').hover(function() { const nickname = config.nickname || '宝宝'; messageSystem.showNormal(`${nickname}不喜欢我了吗...`, 3000); }); $('.waifu-tool .el-icon-switch-button').click(function() { if (typeof showMessage === 'function') { showMessage('我们还能再见面的吧…', 3000, true); } setTimeout(function() { $('.waifu').fadeOut(500); }, 3000); }); reminderSystem = new HealthReminderSystem(); reminderSystem.init(); // 初始化待办系统 todoSystem = new TodoReminderSystem(); todoSystem.init(); // 初始化拖拽停靠功能 initDragDocking(); // 赞赏面板事件 $('#donateClose').click(function() { $('#donatePanel').fadeOut(300); }); $('#donatePanel').click(function(e) { if (e.target.id === 'donatePanel') { $('#donatePanel').fadeOut(300); } }); }, 2000); } setTimeout(() => { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } }, config.loadDelay); console.log('[Live2D] 脚本加载完成,等待初始化...'); })();