【微赞直播】801热度自动增长管理器

自动规划并执行热度增长计划,自动更新热度值并保存(增加快捷设置人数功能)

// ==UserScript==
// @name         【微赞直播】801热度自动增长管理器
// @namespace    http://tampermonkey.net/
// @version      1.4.3
// @description  自动规划并执行热度增长计划,自动更新热度值并保存(增加快捷设置人数功能)
// @author       明灯花月夜
// @match        https://live.vzan.com/admin/index.html?*
// @grant        GM_addStyle
// @run-at       document-idle
// @license MIT
// ==/UserScript==
(function() {
    'use strict';

    // 获取当前页面的锚点部分
    const hash = window.location.hash;

    // 检查锚点部分是否包含特定路径
    if (hash.includes("DataSetting/ChangeTopicPv")) {
        console.log("匹配成功,当前页面包含 TopicManage/ChangeTopicPv!");
        // 在这里添加你需要执行的代码
    } else {
        console.log("未找到匹配路径,退出脚本");
        return; // 如果未找到,直接返回
    }

    // 添加自定义样式
    GM_addStyle(`
        #heat-manager {
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 9999;
            background-color: #fff;
            border-radius: 8px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.15);
            padding: 20px;
            width: 360px;
            font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
            border: 1px solid #ebeef5;
            transform: translateY(0);
            transition: transform 0.3s ease;
        }
        #heat-manager.minimized {
            width: 200px;
            height: 50px;
            overflow: hidden;
        }
        .manager-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
            padding-bottom: 12px;
            border-bottom: 1px solid #eee;
        }
        .manager-title {
            display: flex;
            align-items: center;
            color: #303133;
            font-size: 16px;
            font-weight: 600;
            margin: 0;
        }
        .manager-title svg {
            margin-right: 10px;
            width: 24px;
            height: 24px;
            fill: #409EFF;
        }
        .manager-actions {
            display: flex;
            gap: 8px;
        }
        .manager-btn {
            width: 32px;
            height: 32px;
            border-radius: 4px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.3s;
            background: #f5f7fa;
            border: 1px solid #dcdfe6;
        }
        .heat-form-group {
            margin-bottom: 18px;
        }
        .heat-form-group label {
            display: block;
            margin-bottom: 8px;
            color: #606266;
            font-size: 14px;
            font-weight: 500;
        }
        .heat-form-group input {
            width: 100%;
            padding: 12px 15px;
            border: 1px solid #dcdfe6;
            border-radius: 4px;
            font-size: 14px;
            color: #606266;
            transition: border-color 0.3s;
            box-sizing: border-box;
        }
        .heat-form-group input:focus {
            outline: none;
            border-color: #409EFF;
            box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
        }
        .heat-btn-group {
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
            margin-top: 10px;
        }
        .heat-btn {
            flex: 1;
            padding: 8px;
            border-radius: 4px;
            border: none;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.3s;
            min-width: 48px;
        }
        .heat-btn-primary {
            background: linear-gradient(135deg, #409EFF, #66b1ff);
            color: white;
            box-shadow: 0 2px 6px rgba(64, 158, 255, 0.3);
        }
        .heat-btn-primary:hover {
            background: linear-gradient(135deg, #66b1ff, #409EFF);
            box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
        }
        .heat-btn-warning {
            background: linear-gradient(135deg, #e6a23c, #ebb563);
            color: white;
        }
        .heat-btn-warning:hover {
            background: linear-gradient(135deg, #ebb563, #e6a23c);
        }
        .heat-btn-default {
            background: #f4f4f5;
            color: #606266;
        }
        .heat-btn-default:hover {
            background: #e9e9eb;
        }
        .heat-result {
            margin-top: 20px;
            padding: 18px;
            background: linear-gradient(to right, #f0f7ff, #ecf5ff);
            border-radius: 8px;
            border-left: 4px solid #409EFF;
        }
        .heat-result p {
            margin: 8px 0;
            font-size: 14px;
            color: #303133;
            display: flex;
            justify-content: space-between;
        }
        .heat-result .result-label {
            color: #606266;
        }
        .heat-result .result-value {
            font-weight: 600;
            color: #409EFF;
        }
        .status-indicator {
            display: inline-block;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            margin-right: 8px;
        }
        .status-idle {
            background-color: #909399;
        }
        .status-running {
            background-color: #67C23A;
            animation: pulse 1.5s infinite;
        }
        @keyframes pulse {
            0% { opacity: 1; }
            50% { opacity: 0.4; }
            100% { opacity: 1; }
        }
        .progress-container {
            margin-top: 15px;
            background: #ebeef5;
            border-radius: 4px;
            height: 8px;
            overflow: hidden;
        }
        .progress-bar {
            height: 100%;
            background: linear-gradient(90deg, #409EFF, #66b1ff);
            border-radius: 4px;
            transition: width 0.5s ease;
        }
        .notification {
            position: fixed;
            top: 70px;
            right: 30px;
            padding: 15px 20px;
            border-radius: 6px;
            background: #fff;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            border-left: 4px solid #67C23A;
            transform: translateX(120%);
            transition: transform 0.4s ease;
            z-index: 10000;
            max-width: 300px;
        }
        .notification.show {
            transform: translateX(0);
        }
        .notification-title {
            font-weight: 600;
            margin-bottom: 5px;
            color: #303133;
        }
        .notification-content {
            font-size: 14px;
            color: #606266;
        }
    `);

    // 创建UI界面
    const managerHTML = `
        <div id="heat-manager">
            <div class="manager-header">
                <h3 class="manager-title">
                    <svg viewBox="0 0 24 24">
                        <path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm0 18c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8zm.5-13H11v6l5.2 3.2.8-1.3-4.5-2.7V7z"/>
                    </svg>
                    801热度自动增长管理器
                    <br \>请勿关闭此网页
                </h3>
                <div class="manager-actions">
                    <div class="manager-btn" id="minimize-btn">
                        <svg width="16" height="16" viewBox="0 0 24 24"><path d="M19 13H5v-2h14v2z"/></svg>
                    </div>
                    <div class="manager-btn" id="close-btn">
                        <svg width="16" height="16" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
                    </div>
                </div>
            </div>
            <div class="heat-form-group">
                <label for="target-time">目标时间</label>
                <input type="datetime-local" id="target-time">
            </div>
            <div class="heat-form-group">
                <label for="target-heat">目标热度值</label>
                <input type="number" id="target-heat" min="0" placeholder="输入目标热度数值" value="5000">
                <div class="heat-btn-group" id="quick-setting-buttons">
                    <button class="heat-btn heat-btn-default" data-target="3000">3k</button>
                    <button class="heat-btn heat-btn-default" data-target="5000">5k</button>
                    <button class="heat-btn heat-btn-default" data-target="6000">6k</button>
                    <button class="heat-btn heat-btn-default" data-target="7000">7k</button>
                    <button class="heat-btn heat-btn-default" data-target="8000">8k</button>
                </div>
            </div>
            <div class="heat-btn-group">
                <button id="start-btn" class="heat-btn heat-btn-primary">开始计划</button>
                <button id="stop-btn" class="heat-btn heat-btn-default" disabled>停止</button>
            </div>
            <div id="heat-result" class="heat-result">
                <p>
                    <span class="result-label">当前状态:</span>
                    <span id="status">
                        <span class="status-indicator status-idle"></span>
                        <span>未开始</span>
                    </span>
                </p>
                <p>
                    <span class="result-label">当前热度:</span>
                    <span id="current-heat" class="result-value">0</span>
                </p>
                <p>
                    <span class="result-label">目标热度:</span>
                    <span id="result-target-heat" class="result-value">0</span>
                </p>
                <p>
                    <span class="result-label">目标时间:</span>
                    <span id="result-target-time" class="result-value">--</span>
                </p>
                <p>
                    <span class="result-label">每分钟增加:</span>
                    <span id="heat-per-minute" class="result-value">0</span>
                </p>
                <p>
                    <span class="result-label">总增加量:</span>
                    <span id="total-increase" class="result-value">0</span>
                </p>
                <p>
                    <span class="result-label">剩余热度:</span>
                    <span id="remaining-heat" class="result-value">0</span>
                </p>
                <p>
                    <span class="result-label">下次更新:</span>
                    <span id="next-update" class="result-value">--</span>
                </p>
                <div class="progress-container">
                    <div id="progress-bar" class="progress-bar" style="width: 0%"></div>
                </div>
            </div>
        </div>
        <div id="notification" class="notification">
            <div class="notification-title">操作成功</div>
            <div class="notification-content">热度值已更新并保存</div>
        </div>
    `;

    // 将UI添加到页面
    document.body.insertAdjacentHTML('beforeend', managerHTML);

    // 获取UI元素
    const heatManager = document.getElementById('heat-manager');
    const minimizeBtn = document.getElementById('minimize-btn');
    const closeBtn = document.getElementById('close-btn');
    const targetTimeInput = document.getElementById('target-time');
    const targetHeatInput = document.getElementById('target-heat');
    const quickSettingButtons = document.getElementById('quick-setting-buttons');
    const startBtn = document.getElementById('start-btn');
    const stopBtn = document.getElementById('stop-btn');
    const statusElem = document.getElementById('status');
    const currentHeatElem = document.getElementById('current-heat');
    const resultTargetHeatElem = document.getElementById('result-target-heat');
    const resultTargetTimeElem = document.getElementById('result-target-time');
    const heatPerMinuteElem = document.getElementById('heat-per-minute');
    const totalIncreaseElem = document.getElementById('total-increase');
    const remainingHeatElem = document.getElementById('remaining-heat');
    const nextUpdateElem = document.getElementById('next-update');
    const progressBar = document.getElementById('progress-bar');
    const notification = document.getElementById('notification');

    // 状态变量
    let timer = null;
    let nextUpdateTime = null;
    let heatPerMinute = 0;
    let targetHeat = 0;
    let targetTime = null;
    let currentHeat = 0;
    let isRunning = false;
    let planStartTime = null; // 计划开始时间

    // 设置目标时间为1小时后(默认值)
    const now = new Date();
    const oneHourLater = new Date(now.getTime() + 60 * 60 * 1000);
    targetTimeInput.value = formatDateTimeLocal(oneHourLater);

    // 获取当前热度输入框
    function getHeatInput() {
        // 增加容错逻辑,应对框架动态渲染
        const inputs = document.querySelectorAll('input[placeholder="输入当前热度数值"]');
        // 优先选择可见元素
        for (const input of inputs) {
            if (input.offsetParent !== null) {
                return input;
            }
        }
        return null;
    }

    // 获取保存按钮
    function getSaveButton() {
        const buttons = document.querySelectorAll('button.el-button--primary.el-button--small');
        for (const button of buttons) {
            if (button.textContent.trim() === '保存') {
                return button;
            }
        }
        return null;
    }

    // 显示通知
    function showNotification(message, isSuccess = true) {
        const title = notification.querySelector('.notification-title');
        const content = notification.querySelector('.notification-content');
        if (isSuccess) {
            notification.style.borderLeftColor = '#67C23A';
            title.textContent = '操作成功';
        } else {
            notification.style.borderLeftColor = '#F56C6C';
            title.textContent = '操作失败';
        }
        content.textContent = message;
        notification.classList.add('show');
        setTimeout(() => {
            notification.classList.remove('show');
        }, 3000);
    }

    // 更新页面上的热度值(针对框架网站特别处理)
    function updateHeatValue(value) {
        const heatInput = getHeatInput();
        if (!heatInput) return false;

        try {
            // 1. 设置输入框的值
            heatInput.value = value;

            // 2. 更新ARIA属性
            heatInput.setAttribute('aria-valuenow', value);

            // 3. 创建并触发input事件
            const inputEvent = new InputEvent('input', {
                bubbles: true,
                composed: true,
                data: value.toString(),
                isComposing: false
            });

            // 4. 创建并触发change事件
            const changeEvent = new Event('change', {
                bubbles: true,
                composed: true
            });

            // 5. 执行事件触发
            heatInput.dispatchEvent(inputEvent);
            heatInput.dispatchEvent(changeEvent);

            // 6. 特殊处理:尝试调用框架的数据绑定更新方法(以Vue为例)
            const vueInstance = heatInput.__vue__;
            if (vueInstance && typeof vueInstance.$emit === 'function') {
                vueInstance.$emit('input', value);
                vueInstance.$emit('change', value);
            }

            return true;
        } catch (error) {
            console.error('更新热度值时发生错误:', error);
            return false;
        }
    }

    // 点击保存按钮
    function clickSaveButton() {
        const saveButton = getSaveButton();
        if (saveButton) {
            // 模拟点击
            saveButton.click();
            return true;
        }
        return false;
    }

    // 执行一次更新操作
    function performUpdate() {
        if (!isRunning) return;

        // 获取当前时间
        const now = new Date();

        // 检查是否达到目标时间
        if (now >= targetTime) {
            // 强制将热度设置为目标值
            if (updateHeatValue(targetHeat)) {
                clickSaveButton();
                showNotification('计划已完成,热度值已设置为目标值');
            } else {
                showNotification('热度输入框未找到或更新失败', false);
            }

            // 强制将进度条设置为100%
            progressBar.style.width = '100%';
            remainingHeatElem.textContent = '0';

            stopPlan();
            return;
        }

        // 计算新的热度值
        const minutesPassed = Math.floor((now - planStartTime) / (1000 * 60));
        const newHeat = Math.min(
            Math.floor(currentHeat + minutesPassed * heatPerMinute),
            targetHeat
        );

        // 更新UI
        currentHeatElem.textContent = newHeat;

        // 计算进度百分比(强制在0-100之间)
        let progress = 0;
        const totalIncrease = targetHeat - currentHeat;
        if (totalIncrease > 0) {
            progress = Math.min(100, Math.max(0, Math.floor((newHeat - currentHeat) / totalIncrease * 100)));
        } else {
            progress = 100; // 当前热度已经等于目标热度
        }
        progressBar.style.width = `${progress}%`;

        // 更新剩余热度显示
        const remainingHeat = targetHeat - newHeat;
        remainingHeatElem.textContent = remainingHeat;

        // 更新页面上的热度值
        if (updateHeatValue(newHeat)) {
            // 点击保存按钮
            if (clickSaveButton()) {
                showNotification(`热度值更新为: ${newHeat}`);
            } else {
                showNotification('保存按钮未找到', false);
            }
        } else {
            showNotification('热度输入框未找到或更新失败', false);
        }

        // 计算下一次更新时间(每分钟的整点秒)
        nextUpdateTime = new Date(now);
        nextUpdateTime.setSeconds(0);
        nextUpdateTime.setMilliseconds(0);
        nextUpdateTime.setMinutes(nextUpdateTime.getMinutes() + 1);

        // 更新UI显示下一次更新时间
        nextUpdateElem.textContent = formatTime(nextUpdateTime);

        // 设置下一次更新
        const timeUntilNextUpdate = nextUpdateTime - now;
        timer = setTimeout(performUpdate, timeUntilNextUpdate);
    }

    // 开始计划
    function startPlan() {
        const targetTimeValue = targetTimeInput.value;
        const targetHeatValue = parseInt(targetHeatInput.value);

        if (!targetTimeValue) {
            showNotification('请选择目标时间', false);
            return;
        }

        if (isNaN(targetHeatValue) || targetHeatValue < 0) {
            showNotification('请输入有效的目标热度值', false);
            return;
        }

        const heatInput = getHeatInput();
        if (!heatInput) {
            showNotification('未找到热度输入框,请确认页面是否正确加载', false);
            return;
        }

        const currentHeatValue = parseInt(heatInput.value) || 0;

        if (currentHeatValue >= targetHeatValue) {
            showNotification('目标热度值必须大于当前热度值', false);
            return;
        }

        targetTime = new Date(targetTimeValue);
        const now = new Date();

        if (targetTime <= now) {
            showNotification('目标时间必须晚于当前时间', false);
            return;
        }

        // 计算时间差(分钟)
        const timeDiff = (targetTime - now) / (1000 * 60);
        const totalIncrease = targetHeatValue - currentHeatValue;
        heatPerMinute = Math.ceil(totalIncrease / timeDiff);

        // 设置状态
        isRunning = true;
        currentHeat = currentHeatValue;
        targetHeat = targetHeatValue;
        planStartTime = new Date(); // 设置计划开始时间

        // 更新UI
        statusElem.innerHTML = '<span class="status-indicator status-running"></span><span>运行中</span>';
        currentHeatElem.textContent = currentHeat;
        resultTargetHeatElem.textContent = targetHeat;
        resultTargetTimeElem.textContent = formatDateTime(targetTime);
        heatPerMinuteElem.textContent = heatPerMinute;
        totalIncreaseElem.textContent = totalIncrease;
        remainingHeatElem.textContent = totalIncrease;
        nextUpdateElem.textContent = '正在计算...';
        progressBar.style.width = '0%';

        // 启用/禁用按钮
        startBtn.disabled = true;
        stopBtn.disabled = false;
        targetTimeInput.disabled = true;
        targetHeatInput.disabled = true;

        // 执行第一次更新
        performUpdate();
    }

    // 停止计划
    function stopPlan() {
        if (timer) {
            clearTimeout(timer);
            timer = null;
        }
        isRunning = false;

        // 更新UI
        statusElem.innerHTML = '<span class="status-indicator status-idle"></span><span>已停止</span>';
        startBtn.disabled = false;
        stopBtn.disabled = true;
        targetTimeInput.disabled = false;
        targetHeatInput.disabled = false;
    }

    // 快捷设置目标热度
    function setQuickTarget(target) {
        // 计算正负200之间的随机数
        const randomOffset = Math.floor(Math.random() * 400) - 200;
        const newTarget = target + randomOffset;

        // 确保目标热度不小于0
        const finalTarget = Math.max(0, newTarget);

        // 更新输入框
        targetHeatInput.value = finalTarget;
    }

    // 辅助函数:格式化日期时间
    function formatDateTime(date) {
        return date.toLocaleString('zh-CN', {
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit'
        }).replace(/\//g, '-');
    }

    // 辅助函数:格式化时间
    function formatTime(date) {
        return date.toLocaleTimeString('zh-CN', {
            hour: '2-digit',
            minute: '2-digit',
            second: '2-digit'
        });
    }

    // 辅助函数:格式化日期时间为本地输入格式
    function formatDateTimeLocal(date) {
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, '0');
        const day = String(date.getDate()).padStart(2, '0');
        const hours = String(date.getHours()).padStart(2, '0');
        const minutes = String(date.getMinutes()).padStart(2, '0');
        return `${year}-${month}-${day}T${hours}:${minutes}`;
    }

    // 事件监听
    startBtn.addEventListener('click', startPlan);
    stopBtn.addEventListener('click', stopPlan);
    minimizeBtn.addEventListener('click', function() {
        heatManager.classList.toggle('minimized');
    });
    closeBtn.addEventListener('click', function() {
        stopPlan();
        heatManager.remove();
    });

    // 快捷按钮点击事件
    quickSettingButtons.addEventListener('click', function(e) {
        if (e.target.closest('button')) {
            const target = parseInt(e.target.closest('button').dataset.target);
            setQuickTarget(target);
        }
    });

    // 初始化显示当前热度
    const heatInput = getHeatInput();
    if (heatInput) {
        currentHeatElem.textContent = heatInput.value || '0';
    }
})();