[银河奶牛]自用脚本

个人专用脚本,适用于 Milky Way Idle 游戏

当前为 2025-07-14 提交的版本,查看 最新版本

// ==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();
        }
    });

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址