// ==UserScript==
// @name [MWI]personal use
// @name:zh-CN [银河奶牛]自用脚本
// @namespace http://tampermonkey.net/
// @version 0.1.3
// @description 个人脚本,适用于 Milky Way Idle 游戏
// @description:zh-CN 个人专用脚本,适用于 Milky Way Idle 游戏
// @author deric
// @license MIT
// @match https://www.milkywayidle.com/game*
// @grant GM_registerMenuCommand
// ==/UserScript==
(function() {
'use strict';
// Tampermonkey插件管理界面显示“设置”按钮
if (typeof GM_registerMenuCommand !== 'undefined') {
// 用setTimeout确保页面环境已加载
setTimeout(() => {
GM_registerMenuCommand('设置', function() {
// 网页内弹窗
try {
let exist = document.getElementById('mwiSettingPopup');
if (exist) exist.remove();
const popup = document.createElement('div');
popup.id = 'mwiSettingPopup';
popup.style.position = 'fixed';
popup.style.top = '120px';
popup.style.left = '50%';
popup.style.transform = 'translateX(-50%)';
popup.style.background = 'white';
popup.style.border = '2px solid #888';
popup.style.boxShadow = '0 2px 12px rgba(0,0,0,0.2)';
popup.style.zIndex = 99999;
popup.style.padding = '24px 32px';
popup.style.minWidth = '300px';
popup.innerHTML = `<div style='text-align:right;'><button id='closeMwiSettingPopup'>关闭</button></div><h3 style='margin:8px 0 16px 0;'>设置</h3>
<div style='margin-bottom:12px;'>
<label><input type='checkbox' id='mwiMonitorPlayer'/> 是否启动监控人数</label>
</div>
<div style='margin-bottom:12px;'>
<label><input type='checkbox' id='mwiMonitorNetWorth'/> 是否启动监控净资产</label>
</div>
<div style='color:#888;font-size:12px;'>设置会自动保存</div>`;
document.body.appendChild(popup);
document.getElementById('closeMwiSettingPopup').onclick = function() {
popup.remove();
};
// 初始化复选框状态
document.getElementById('mwiMonitorPlayer').checked = localStorage.getItem('mwiMonitorPlayer') !== 'false';
document.getElementById('mwiMonitorNetWorth').checked = localStorage.getItem('mwiMonitorNetWorth') === 'true';
// 监听变更
document.getElementById('mwiMonitorPlayer').onchange = function() {
localStorage.setItem('mwiMonitorPlayer', this.checked);
};
document.getElementById('mwiMonitorNetWorth').onchange = function() {
localStorage.setItem('mwiMonitorNetWorth', this.checked);
};
} catch(e) {
alert('弹窗创建失败,请刷新页面重试!');
}
});
}, 1000);
}
// 你的代码写在这里
function savePlayerNumber() {
const el = document.querySelector('div.Header_playerCount__1TDTK');
if (!el) return;
const number = parseInt(el.textContent.replace(/\D/g, ''), 10);
const now = new Date().toISOString();
const data = { time: now, number };
let arr = [];
try {
arr = JSON.parse(localStorage.getItem('playernumber') || '[]');
if (!Array.isArray(arr)) arr = [];
} catch(e) { arr = []; }
arr.push(data);
localStorage.setItem('playernumber', JSON.stringify(arr));
}
// 新增:根据当前网址获取key
function getNetWorthKey() {
const url = window.location.href;
const match = url.match(/(\d{6})(?!.*\d)/);
return match ? match[1] : 'default';
}
// 新增:保存净资产(分key)
function saveNetWorth() {
const el = document.querySelector('#toggleNetWorth');
if (!el) return;
// 提取数字和单位
const match = el.textContent.replace(/,/g, '').match(/([\d.]+)\s*([KMBT]?)/i);
let number = null;
if (match) {
number = parseFloat(match[1]);
const unit = match[2]?.toUpperCase();
if (unit === 'K') number *= 1e3;
else if (unit === 'M') number *= 1e6;
else if (unit === 'B') number *= 1e9;
else if (unit === 'T') number *= 1e12;
}
const now = new Date().toISOString();
const data = { time: now, number };
let arr = [];
const key = 'networth_' + getNetWorthKey();
try {
arr = JSON.parse(localStorage.getItem(key) || '[]');
if (!Array.isArray(arr)) arr = [];
} catch(e) { arr = []; }
arr.push(data);
localStorage.setItem(key, JSON.stringify(arr));
}
// 修改按钮显示逻辑,支持显示多条记录
function createShowButton() {
// 判断设置
if (localStorage.getItem('mwiMonitorPlayer') === 'false') return;
const target = document.querySelector("#root > div > div > div.GamePage_headerPanel__1T_cA > div > div.Header_leftHeader__PkRWX > div.Header_navLogoAndPlayerCount__2earI > div.Header_playerCount__1TDTK");
if (!target || document.getElementById('showPlayerNumberBtn')) return;
const btn = document.createElement('button');
btn.id = 'showPlayerNumberBtn';
btn.textContent = '显示玩家人数记录';
btn.style.marginLeft = '8px';
btn.onclick = function() {
let data = localStorage.getItem('playernumber');
let arr = [];
if (data) {
try {
arr = JSON.parse(data);
if (!Array.isArray(arr)) arr = [];
} catch(e) { arr = []; }
}
// 默认时间范围(天)
let rangeDays = parseInt(localStorage.getItem('mwiPlayerNumberRangeDays')) || 1;
// 生成时间范围按钮
const ranges = [1, 3, 7, 14, 30];
let rangeBtns = '<div style="margin-bottom:8px;">';
for(const d of ranges){
rangeBtns += `<button class='rangeBtn' data-days='${d}' style='margin-right:4px;'>${d}天</button>`;
}
rangeBtns += '</div>';
// 生成折线图HTML
let html = rangeBtns;
html += `<div id='chartContainer'></div>`;
// 网页内部弹窗
let exist = document.getElementById('playerNumberPopup');
if (exist) exist.remove();
const popup = document.createElement('div');
popup.id = 'playerNumberPopup';
popup.style.position = 'fixed';
popup.style.top = '80px';
popup.style.left = '40px';
popup.style.background = 'white';
popup.style.border = '2px solid #888';
popup.style.boxShadow = '0 2px 12px rgba(0,0,0,0.2)';
popup.style.zIndex = 9999;
popup.style.padding = '16px';
popup.style.maxHeight = '500px';
popup.style.overflow = 'auto';
popup.innerHTML = `<div style='text-align:right;'><button id='closePlayerNumberPopup'>关闭</button></div>${html}`;
document.body.appendChild(popup);
document.getElementById('closePlayerNumberPopup').onclick = function() {
popup.remove();
};
// 绘制折线图函数
function drawChart(days) {
const now = Date.now();
const ms = days * 24 * 60 * 60 * 1000;
const filtered = arr.filter(item => {
const t = new Date(item.time).getTime();
return t >= now - ms;
}).sort((a, b) => new Date(a.time) - new Date(b.time));
const container = document.getElementById('chartContainer');
if(filtered.length > 1){
container.innerHTML = `<canvas id='playerNumberChart' width='500' height='300' style='display:block;'></canvas>`;
}else if(filtered.length === 1){
container.innerHTML = `仅有一条数据:${filtered[0].time} - ${filtered[0].number}`;
return;
}else{
container.innerHTML = '暂无数据';
return;
}
setTimeout(() => {
const canvas = document.getElementById('playerNumberChart');
if (!canvas) return;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 处理数据
const times = filtered.map(item => new Date(item.time));
const numbers = filtered.map(item => item.number);
// 横坐标:小时:分
let labels;
if(days <= 3){
labels = times.map(t => `${t.getHours()}:` + (t.getMinutes()<10?`0${t.getMinutes()}`:t.getMinutes()));
}else{
labels = times.map(t => `${t.getMonth()+1}-${t.getDate()}`);
}
// 纵坐标范围,增加上下边距
let minY = Math.min(...numbers);
let maxY = Math.max(...numbers);
const steps = 5;
const step = (maxY - minY) / steps || 1;
minY = Math.max(0, minY - step);
maxY = maxY + step;
const padding = 55;
const w = canvas.width - padding*2;
const h = canvas.height - padding*2;
ctx.font = '12px sans-serif';
// 横坐标等比例
const minTime = times[0].getTime();
const maxTime = times[times.length-1].getTime();
const timeSpan = maxTime - minTime || 1;
// 绘制坐标轴
ctx.strokeStyle = '#333';
ctx.beginPath();
ctx.moveTo(padding, padding);
ctx.lineTo(padding, padding + h);
ctx.lineTo(padding + w, padding + h);
ctx.stroke();
// Y轴刻度
ctx.fillStyle = '#333';
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
for(let i=0;i<=5;i++){
const y = padding + h - h*i/5;
const val = Math.round(minY + (maxY-minY)*i/5);
ctx.fillText(val, padding-5, y);
ctx.strokeStyle = '#eee';
ctx.beginPath();
ctx.moveTo(padding, y);
ctx.lineTo(padding+w, y);
ctx.stroke();
}
// X轴刻度(等比例)
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
for(let i=0;i<times.length;i++){
const t = times[i].getTime();
const x = padding + w*(t-minTime)/timeSpan;
ctx.fillStyle = '#333';
// 只显示首、尾、每隔5个点的标签,避免重叠
if(i===0 || i===times.length-1 || (times.length>10 && i%5===0)){
ctx.fillText(labels[i], x, padding+h+5);
}
}
// 折线(等比例)
ctx.strokeStyle = '#007bff';
ctx.beginPath();
for(let i=0;i<numbers.length;i++){
const t = times[i].getTime();
const x = padding + w*(t-minTime)/timeSpan;
const y = padding + h - h*(numbers[i]-minY)/(maxY-minY||1);
if(i===0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
// 点(等比例)
ctx.fillStyle = '#007bff';
for(let i=0;i<numbers.length;i++){
const t = times[i].getTime();
const x = padding + w*(t-minTime)/timeSpan;
const y = padding + h - h*(numbers[i]-minY)/(maxY-minY||1);
ctx.beginPath();
ctx.arc(x, y, 3, 0, 2*Math.PI);
ctx.fill();
}
}, 0);
}
// 默认绘制1天
drawChart(rangeDays);
// 按钮事件
popup.querySelectorAll('.rangeBtn').forEach(btn => {
btn.onclick = function(){
const days = parseInt(this.getAttribute('data-days'));
localStorage.setItem('mwiPlayerNumberRangeDays', days);
drawChart(days);
highlightRangeBtn(days);
};
});
};
target.parentNode.appendChild(btn);
}
// 新增:显示净资产历史折线图按钮(分key)
function createShowNetWorthButton() {
// 判断设置
if (localStorage.getItem('mwiMonitorNetWorth') !== 'true') return;
const target = document.querySelector("#toggleNetWorth");
if (!target || document.getElementById('showNetWorthBtn')) return;
const btn = document.createElement('button');
btn.id = 'showNetWorthBtn';
const key = getNetWorthKey();
btn.textContent = '显示净资产记录';
btn.style.marginLeft = '8px';
btn.onclick = function() {
const key = 'networth_' + getNetWorthKey();
let data = localStorage.getItem(key);
let arr = [];
if (data) {
try {
arr = JSON.parse(data);
if (!Array.isArray(arr)) arr = [];
} catch(e) { arr = []; }
}
// 默认时间范围(天)
let rangeDays = parseInt(localStorage.getItem('mwiNetWorthRangeDays')) || 1;
// 生成时间范围按钮
const ranges = [1, 3, 7, 14, 30];
let rangeBtns = '<div style="margin-bottom:8px;">';
for(const d of ranges){
rangeBtns += `<button class='rangeBtnNet' data-days='${d}' style='margin-right:4px;'>${d}天</button>`;
}
rangeBtns += '</div>';
// 生成折线图HTML
let html = rangeBtns;
html += `<div id='chartNetContainer'></div>`;
// 网页内部弹窗
let exist = document.getElementById('netWorthPopup');
if (exist) exist.remove();
const popup = document.createElement('div');
popup.id = 'netWorthPopup';
popup.style.position = 'fixed';
popup.style.top = '80px';
popup.style.left = '40px';
popup.style.background = 'white';
popup.style.border = '2px solid #888';
popup.style.boxShadow = '0 2px 12px rgba(0,0,0,0.2)';
popup.style.zIndex = 9999;
popup.style.padding = '16px';
popup.style.maxHeight = '500px';
popup.style.overflow = 'visible';
popup.innerHTML = `<div style='text-align:right;'><button id='closeNetWorthPopup'>关闭</button></div><h4 style='margin:8px 0 16px 0;'>净资产记录</h4>${html}`;
document.body.appendChild(popup);
document.getElementById('closeNetWorthPopup').onclick = function() {
popup.remove();
};
// 绘制折线图函数
function drawChart(days) {
const now = Date.now();
const ms = days * 24 * 60 * 60 * 1000;
const filtered = arr.filter(item => {
const t = new Date(item.time).getTime();
return t >= now - ms;
}).sort((a, b) => new Date(a.time) - new Date(b.time));
const container = document.getElementById('chartNetContainer');
if(filtered.length > 1){
container.innerHTML = `<canvas id='netWorthChart' width='550' height='320' style='display:block;'></canvas>`;
}else if(filtered.length === 1){
container.innerHTML = `仅有一条数据:${(filtered[0].time)} - ${Math.round(filtered[0].number/1e6)}M`;
return;
}else{
container.innerHTML = '暂无数据';
return;
}
setTimeout(() => {
const canvas = document.getElementById('netWorthChart');
if (!canvas) return;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 处理数据
const times = filtered.map(item => new Date(item.time));
const numbers = filtered.map(item => item.number/1e6); // 以M为单位
// 横坐标:根据天数显示小时或日期
let labels;
if(days <= 3){
labels = times.map(t => `${t.getHours()}:` + (t.getMinutes()<10?`0${t.getMinutes()}`:t.getMinutes()));
}else{
labels = times.map(t => `${t.getMonth()+1}-${t.getDate()}`);
}
// 纵坐标范围,增加上下边距
let minY = Math.min(...numbers);
let maxY = Math.max(...numbers);
const steps = 5;
const step = (maxY - minY) / steps || 1;
minY = Math.max(0, minY - step);
maxY = maxY + step;
const padding = 55;
const w = canvas.width - padding*2;
const h = canvas.height - padding*2;
ctx.font = '12px sans-serif';
// 横坐标等比例
const minTime = times[0].getTime();
const maxTime = times[times.length-1].getTime();
const timeSpan = maxTime - minTime || 1;
// 绘制坐标轴
ctx.strokeStyle = '#333';
ctx.beginPath();
ctx.moveTo(padding, padding);
ctx.lineTo(padding, padding + h);
ctx.lineTo(padding + w, padding + h);
ctx.stroke();
// Y轴刻度
ctx.fillStyle = '#333';
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
for(let i=0;i<=5;i++){
const y = padding + h - h*i/5;
const val = minY + (maxY-minY)*i/5;
ctx.fillText(Math.round(val)+'M', padding-5, y);
ctx.strokeStyle = '#eee';
ctx.beginPath();
ctx.moveTo(padding, y);
ctx.lineTo(padding+w, y);
ctx.stroke();
}
// X轴刻度(等比例)
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
for(let i=0;i<times.length;i++){
const t = times[i].getTime();
const x = padding + w*(t-minTime)/timeSpan;
ctx.fillStyle = '#333';
// 只显示首、尾、每隔5个点的标签,避免重叠
if(i===0 || i===times.length-1 || (times.length>10 && i%5===0)){
ctx.fillText(labels[i], x, padding+h+5);
}
}
// 折线(等比例)
ctx.strokeStyle = '#28a745';
ctx.beginPath();
for(let i=0;i<numbers.length;i++){
const t = times[i].getTime();
const x = padding + w*(t-minTime)/timeSpan;
const y = padding + h - h*(numbers[i]-minY)/(maxY-minY||1);
if(i===0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
// 点(等比例)
ctx.fillStyle = '#28a745';
for(let i=0;i<numbers.length;i++){
const t = times[i].getTime();
const x = padding + w*(t-minTime)/timeSpan;
const y = padding + h - h*(numbers[i]-minY)/(maxY-minY||1);
ctx.beginPath();
ctx.arc(x, y, 3, 0, 2*Math.PI);
ctx.fill();
}
}, 0);
}
// 默认绘制1天
drawChart(rangeDays);
// 高亮当前按钮
function highlightRangeBtnNet(days) {
popup.querySelectorAll('.rangeBtnNet').forEach(btn => {
if(parseInt(btn.getAttribute('data-days')) === days){
btn.style.background = '#e0e0e0';
}else{
btn.style.background = '';
}
});
}
highlightRangeBtnNet(rangeDays);
// 按钮事件
popup.querySelectorAll('.rangeBtnNet').forEach(btn => {
btn.onclick = function(){
const days = parseInt(this.getAttribute('data-days'));
localStorage.setItem('mwiNetWorthRangeDays', days);
drawChart(days);
highlightRangeBtnNet(days);
};
});
};
target.parentNode.appendChild(btn);
}
// 修改定时器和延时调用,根据设置决定是否监控
setTimeout(() => {
if(localStorage.getItem('mwiMonitorPlayer') !== 'false') savePlayerNumber();
if(localStorage.getItem('mwiMonitorNetWorth') === 'true') saveNetWorth();
}, 3000);
setInterval(() => {
if(localStorage.getItem('mwiMonitorPlayer') !== 'false') savePlayerNumber();
if(localStorage.getItem('mwiMonitorNetWorth') === 'true') saveNetWorth();
}, 30 * 60 * 1000);
// 页面加载后添加按钮
window.addEventListener('DOMContentLoaded', () => {
createShowButton();
createShowNetWorthButton();
});
setTimeout(() => {
createShowButton();
createShowNetWorthButton();
}, 2000);
// 首次安装时默认开启所有功能
if (localStorage.getItem('mwiMonitorPlayer') === null) {
localStorage.setItem('mwiMonitorPlayer', 'true');
}
if (localStorage.getItem('mwiMonitorNetWorth') === null) {
localStorage.setItem('mwiMonitorNetWorth', 'true');
}
// 设置变更时刷新按钮显示
window.addEventListener('storage', function(e) {
if (e.key === 'mwiMonitorPlayer' || e.key === 'mwiMonitorNetWorth') {
// 移除旧按钮
const btn1 = document.getElementById('showPlayerNumberBtn');
if (btn1) btn1.remove();
const btn2 = document.getElementById('showNetWorthBtn');
if (btn2) btn2.remove();
// 重新判断并添加
createShowButton();
createShowNetWorthButton();
}
});
})();