// ==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 = `
`;
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}
设置你所在的省份和城市,点击天气图标即可查看天气信息
不用加"省",如:广东、浙江、北京
不用加"市",如:广州、深圳、杭州
提醒时间段:${config.healthReminders.workingHours.start}:00 - ${config.healthReminders.workingHours.end}:00
喝水提醒
休息提醒
坐姿提醒
睡觉提醒
自定义提醒台词
每行一句台词,随机显示。使用 {nickname} 可显示昵称
每行一句台词,随机显示。使用 {nickname} 可显示昵称
可以请
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 = `
`;
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 = `
`;
// 检查是否已经存在看板娘,避免重复创建
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] 脚本加载完成,等待初始化...');
})();