// ==UserScript==
// @name MilkyWayIdle 强化等级通知到QQ (PC端)
// @namespace http://tampermonkey.net/
// @version 1.07
// @description 实时检测强化等级与无所事事的状态通过Qmsg酱发送给QQ
// @author Suyeye * DS
// @match https://www.milkywayidle.com/*
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @connect qmsg.zendee.cn
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const defaultConfig = {
enabled: true,
qmsgKey: '',
idleNotify: true,
enhanceLevel: 6,
panelLeft: window.innerWidth - 300,
panelTop: 20,
panelVisible: true
};
GM_addStyle(`
#config-panel {
position: fixed;
z-index: 9999;
background: rgba(40,40,40,0.9);
color: #fff;
border-radius: 8px;
padding: 15px;
min-width: 250px;
box-shadow: 0 4px 12px rgba(0,0,0,0.25);
backdrop-filter: blur(5px);
cursor: move;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.config-group {
margin: 10px 0;
}
input[type="text"], select {
width: 100%;
padding: 6px;
margin: 4px 0;
background: #333;
border: 1px solid #555;
color: #fff;
}
.hidden {
display: none;
}
/* 按钮样式 */
.panel-button {
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
transition: opacity 0.2s;
margin-left: 5px;
}
.panel-button:hover {
opacity: 0.8;
}
#toggle-enable {
background: #28a745;
}
#toggle-enable.disabled {
background: #6c757d;
}
#toggle-panel {
background: #007bff;
}
#tutorial-button {
background: #6f42c1;
}
#tutorial-button.active {
background: #ffc107;
color: #333;
}
.tutorial-content {
max-height: 300px;
overflow-y: auto;
padding: 10px;
background: rgba(50,50,50,0.8);
border-radius: 5px;
margin-top: 10px;
border: 1px solid #555;
}
.tutorial-content ol {
padding-left: 20px;
}
.tutorial-content li {
margin-bottom: 8px;
}
.tutorial-content a {
color: #80bdff;
text-decoration: none;
}
.tutorial-content a:hover {
text-decoration: underline;
}
/* 按钮容器 */
.button-container {
display: flex;
}
`);
class NotificationSystem {
constructor() {
this.config = {...defaultConfig, ...GM_getValue('config', {})};
this.lastState = { type: null, level: null };
this.tutorialActive = false;
this.observer = null;
this.targetNode = null;
this.initUI();
this.initKeyboardShortcut();
this.initObserver();
}
initUI() {
this.panel = document.createElement('div');
this.panel.id = 'config-panel';
this.panel.style.left = `${this.config.panelLeft}px`;
this.panel.style.top = `${this.config.panelTop}px`;
if (!this.config.panelVisible) {
this.panel.classList.add('hidden');
}
this.createPanelContent();
document.body.appendChild(this.panel);
this.panel.querySelector('#toggle-enable').addEventListener('click', () => this.toggleEnable());
this.panel.querySelector('#toggle-panel').addEventListener('click', () => this.togglePanelVisibility());
this.panel.querySelector('#tutorial-button').addEventListener('click', () => this.toggleTutorial());
this.panel.querySelector('#qmsg-key').addEventListener('input', e => this.saveConfig('qmsgKey', e.target.value));
this.panel.querySelector('#idle-notify').addEventListener('change', e => this.saveConfig('idleNotify', e.target.checked));
this.panel.querySelector('#enhance-level').addEventListener('change', e => this.saveConfig('enhanceLevel', parseInt(e.target.value)));
let isDragging = false;
let offset = [0,0];
this.panel.addEventListener('mousedown', e => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT' || e.target.tagName === 'BUTTON') {
return;
}
isDragging = true;
offset = [
e.clientX - this.panel.offsetLeft,
e.clientY - this.panel.offsetTop
];
this.panel.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', e => {
if (!isDragging) return;
this.panel.style.left = `${e.clientX - offset[0]}px`;
this.panel.style.top = `${e.clientY - offset[1]}px`;
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
this.panel.style.cursor = 'move';
this.saveConfig('panelLeft', this.panel.offsetLeft);
this.saveConfig('panelTop', this.panel.offsetTop);
}
});
}
createPanelContent() {
this.panel.innerHTML = `
<div class="panel-header">
<span>通知配置</span>
<div class="button-container">
<button id="toggle-enable" class="panel-button ${this.config.enabled ? '' : 'disabled'}">
${this.config.enabled ? '启动中' : '禁用中'}
<button id="toggle-panel" class="panel-button">隐藏(F1)</button>
<button id="tutorial-button" class="panel-button">教程</button>
</button>
</div>
</div>
<div class="panel-body">
<div id="settings-content">
<div class="config-group">
<label>Qmsg密钥:</label>
<input type="text" id="qmsg-key" value="${this.config.qmsgKey}" placeholder="输入您的Qmsg密钥">
</div>
<div class="config-group">
<label>
<input type="checkbox" id="idle-notify" ${this.config.idleNotify ? 'checked' : ''}>
启用无所事事通知
</label>
</div>
<div class="config-group">
<label>强化等级通知:</label>
<select id="enhance-level">
<option value="0" ${0 === this.config.enhanceLevel ? 'selected' : ''}>关闭</option>
${Array.from({length: 13}, (_,i) => {
const level = i + 8;
return `<option value="${level}" ${level === this.config.enhanceLevel ? 'selected' : ''}>
+${level}及以上
</option>`;
}).join('')}
</select>
</div>
</div>
<div id="tutorial-content" class="tutorial-content hidden">
<div>基本功能:</div>
<div>检测窗口无所事事或者强化等级给QQ发送消息,须填写Qmsg酱key才能使用。</div>
<div>离线状态无法检测。</div>
<ol>
<li>登陆/注册(不可用)<a href="https://qmsg.zendee.cn/" target="_blank">Qmsg酱</a></li>
<li>添加自己接收用的QQ,并选择一位机器人添加且自动通过申请。</li>
<li>复制我的KEY</li>
<li>在本窗口输入KEY并点击启用按钮</li>
</ol>
<div>※ 建议不要用第一个机器人,如果好友申请没有立刻通过,需要更换一个机器人添加。</div>
<div>※ 第三方机器人每天只允许发送100条消息,因此限制播报起点为+8以上。</div>
</div>
</div>
`;
}
initKeyboardShortcut() {
document.addEventListener('keydown', (e) => {
if (e.key === 'F1') {
e.preventDefault();
this.togglePanelVisibility();
}
});
}
saveConfig(key, value) {
this.config[key] = value;
GM_setValue('config', this.config);
}
toggleEnable() {
this.config.enabled = !this.config.enabled;
this.saveConfig('enabled', this.config.enabled);
const btn = this.panel.querySelector('#toggle-enable');
btn.textContent = this.config.enabled ? '启动中' : '禁用中';
btn.classList.toggle('disabled', !this.config.enabled);
}
togglePanelVisibility() {
this.config.panelVisible = !this.config.panelVisible;
this.saveConfig('panelVisible', this.config.panelVisible);
this.panel.classList.toggle('hidden');
const btn = this.panel.querySelector('#toggle-panel');
btn.textContent = this.config.panelVisible ? '隐藏(F1)' : '显示(F1)';
}
toggleTutorial() {
this.tutorialActive = !this.tutorialActive;
const tutorialBtn = this.panel.querySelector('#tutorial-button');
const settingsContent = this.panel.querySelector('#settings-content');
const tutorialContent = this.panel.querySelector('#tutorial-content');
const toggleEnableBtn = this.panel.querySelector('#toggle-enable');
const togglePanelBtn = this.panel.querySelector('#toggle-panel');
tutorialBtn.classList.toggle('active', this.tutorialActive);
settingsContent.classList.toggle('hidden', this.tutorialActive);
tutorialContent.classList.toggle('hidden', !this.tutorialActive);
toggleEnableBtn.classList.toggle('hidden', this.tutorialActive);
togglePanelBtn.classList.toggle('hidden', this.tutorialActive);
}
detectEnhanceLevel(text) {
if (this.config.enhanceLevel === 0) return null;
const match = text.match(/\+(\d+)\b/);
return match && parseInt(match[1]) >= this.config.enhanceLevel ? match[0] : null;
}
handleStateChange(element) {
if (!this.config.enabled) return;
const currentText = element?.textContent?.trim() || "";
const isIdle = this.config.idleNotify && currentText.includes("无所事事");
const enhanceLevel = this.detectEnhanceLevel(currentText);
let shouldSend = false;
let message = "";
if (isIdle && this.lastState.type !== 'idle') {
message = `${new Date().toTimeString().slice(0,5)}分开始闲置啦!`;
this.lastState = { type: 'idle', level: null };
shouldSend = true;
}
else if (enhanceLevel && (
this.lastState.type !== 'enhance' ||
this.lastState.level !== enhanceLevel
)) {
message = currentText;
this.lastState = { type: 'enhance', level: enhanceLevel };
shouldSend = true;
}
else if (!isIdle && !enhanceLevel && this.lastState.type !== null) {
this.lastState = { type: null, level: null };
}
if (shouldSend && this.config.qmsgKey) {
GM_xmlhttpRequest({
method: "GET",
url: `https://qmsg.zendee.cn/send/${this.config.qmsgKey}?msg=${encodeURIComponent(message)}`,
onload: () => console.log(`[${new Date().toLocaleTimeString()}] 通知发送成功`),
onerror: (err) => console.error('发送失败:', err)
});
}
}
initObserver() {
this.findAndObserveTarget();
setInterval(() => {
if (!this.targetNode || !document.body.contains(this.targetNode)) {
console.log('目标元素丢失,重新连接观察器...');
if (this.observer) {
this.observer.disconnect();
this.observer = null;
this.targetNode = null;
}
this.findAndObserveTarget();
}
}, 2000);
}
findAndObserveTarget() {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
this.targetNode = document.querySelector("div.Header_displayName__1hN09");
if (!this.targetNode) {
console.log('未找到状态元素,5秒后重试...');
setTimeout(() => this.findAndObserveTarget(), 5000);
return;
}
console.log('找到状态元素,开始观察...');
this.observer = new MutationObserver(mutations => {
mutations.forEach(() => this.handleStateChange(this.targetNode));
});
this.observer.observe(this.targetNode, {
characterData: true,
subtree: true,
childList: true,
attributes: true,
attributeFilter: ['textContent']
});
this.handleStateChange(this.targetNode);
}
}
new NotificationSystem();
})();