在 github 等找到网易云自动刷量的都是好久之前的了,还有封号的风险

于是弄了这个方便网易云音乐人完成听歌任务
项目部署在 docker 里,借助浏览器播放网易云播放实现刷量
docker 的浏览器是通过 vnc 实现可用网页打开
脚本是用到油猴插件

推荐用小号收藏要刷的歌曲在一个歌单,浏览器打开这个歌单试一下播放
再设置脚本定时播放

注意:端口请改成自己合适的,还有 config 路径

Docker Compose

services: edge-browser:  linuxserver/msedge:latest # Edge浏览器镜像 container_name: edge-browser # 容器名称 hostname: edge-browser # 容器主机名 environment: - PUID=1000 # 用户ID,请根据你的NAS实际用户ID修改 - PGID=1000 # 用户组ID,请根据你的NAS实际用户组ID修改 - TZ=Asia/Shanghai # 时区设置 - LC_ALL=zh_CN.UTF-8 #- CUSTOM_USER=admin #配置访问用户名 已注释 #- PASSWORD=password  #配置访问密码 已注释 - EDGE_CLI=https://music.163.com/ # Edge启动时打开的默认网页 volumes: - ./config:/config # 核心数据持久化,浏览器配置、书签、历史记录等都存在这里 ports: - 3000:3000 # 内部 HTTP 端口。默认为 3000 - 3001:3001 # 内部 HTTPS 端口。默认为 3001,修改冒号左边的端口号。 shm_size: "1gb" # /dev/shm 大小,建议至少 1GB,避免浏览器崩溃 restart: unless-stopped # 除非手动停止,否则容器异常退出后自动重启 

油猴脚本 js

// ==UserScript==
// @name         网易云音乐定时播放暂停(每日重复+无弹窗自动执行)
// @namespace    https://shuffle.com/
// @version      3.0
// @description  适配.btns容器按钮,每日固定时间自动播放/暂停,无弹窗、任务持久化
// @author       You
// @match        *://music.163.com/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // ========== 核心配置 ==========
    const PLAY_PAUSE_SELECTOR = '.btns [data-action="play"].ply.j-flag';
    const BACKUP_SELECTOR = '.btns .ply.j-flag';
    const STORAGE_KEY = 'neteaseMusicTimedTasks'; // 本地存储键名

    // 全局变量:存储重复任务的定时器ID(key: 任务ID, value: intervalId)
    let intervalTasks = {};

    // ========== 工具函数 ==========
    // 1. 等待播放按钮加载
    function waitForPlayBtn() {
        return new Promise(resolve => {
            const findBtn = () => {
                let btn = document.querySelector(PLAY_PAUSE_SELECTOR);
                if (!btn) btn = document.querySelector(BACKUP_SELECTOR);
                return btn;
            };
            const btn = findBtn();
            if (btn) return resolve(btn);

            const observer = new MutationObserver(() => {
                const btn = findBtn();
                if (btn) {
                    observer.disconnect();
                    resolve(btn);
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
        });
    }

    // 2. 无弹窗执行播放/暂停(仅控制台输出日志)
    async function togglePlayPause(action) {
        try {
            const playBtn = await waitForPlayBtn();
            if (!playBtn) {
                console.error('[网易云定时] 未找到播放/暂停按钮');
                return;
            }

            // 状态判断:data-action=pause → 播放中;data-action=play → 暂停中
            const currentAction = playBtn.getAttribute('data-action');
            const isPlaying = currentAction === 'pause';
            const actionText = action === 'play' ? '播放' : '暂停';

            // 自动执行点击,无弹窗
            if (action === 'play' && !isPlaying) {
                playBtn.click();
                console.log(`[网易云定时] ${new Date().toLocaleTimeString()} → 自动${actionText}成功`);
            } else if (action === 'pause' && isPlaying) {
                playBtn.click();
                console.log(`[网易云定时] ${new Date().toLocaleTimeString()} → 自动${actionText}成功`);
            } else {
                console.log(`[网易云定时] ${new Date().toLocaleTimeString()} → 无需执行${actionText}(当前:${isPlaying ? '播放中' : '已暂停'})`);
            }
        } catch (e) {
            console.error('[网易云定时] 执行失败:', e);
        }
    }

    // 3. 计算「首次执行延迟」和「每日重复间隔」
    function getDelayAndInterval(targetHour, targetMinute) {
        const now = new Date();
        const target = new Date();
        target.setHours(targetHour);
        target.setMinutes(targetMinute);
        target.setSeconds(0);
        target.setMilliseconds(0);

        // 首次执行延迟:若目标时间已过,延迟为「明天该时间 - 现在」
        let delay = target.getTime() - now.getTime();
        if (delay < 0) delay += 86400000; // 加24小时(86400000毫秒)

        return {
            firstDelay: delay, // 首次执行延迟(毫秒)
            repeatInterval: 86400000 // 每日重复间隔(24小时)
        };
    }

    // ========== 任务持久化(localStorage) ==========
    // 1. 从本地存储加载任务
    function loadTasks() {
        const tasks = localStorage.getItem(STORAGE_KEY);
        return tasks ? JSON.parse(tasks) : [];
    }

    // 2. 保存任务到本地存储
    function saveTasks(tasks) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
    }

    // ========== 重复任务管理 ==========
    // 1. 添加每日重复任务
    function addDailyTask(hour, minute, action) {
        // 验证时间格式
        if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
            alert('时间格式错误!小时(0-23),分钟(0-59)'); // 仅添加时提示,执行时无弹窗
            return;
        }

        // 生成唯一任务ID
        const taskId = Date.now().toString();
        const { firstDelay, repeatInterval } = getDelayAndInterval(hour, minute);

        // 首次执行 + 每日重复
        const firstTimer = setTimeout(() => {
            // 执行首次操作
            togglePlayPause(action);
            // 设置每日重复
            const intervalId = setInterval(() => {
                togglePlayPause(action);
            }, repeatInterval);
            intervalTasks[taskId] = intervalId;
        }, firstDelay);

        // 存储任务信息
        const task = {
            id: taskId,
            time: `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`,
            action: action === 'play' ? '播放' : '暂停',
            firstTimerId: firstTimer // 首次执行的定时器ID
        };
        const tasks = loadTasks();
        tasks.push(task);
        saveTasks(tasks);

        // 更新UI + 提示添加成功(仅一次)
        updateTaskList();
        alert(`✅ 每日重复任务已添加:每天${task.time}自动${task.action}音乐(页面刷新不丢失)`);
    }

    // 2. 取消单个每日任务
    function cancelDailyTask(taskId) {
        const tasks = loadTasks();
        const taskIndex = tasks.findIndex(t => t.id === taskId);
        if (taskIndex === -1) return;

        // 清除定时器(首次执行+重复执行)
        clearTimeout(tasks[taskIndex].firstTimerId);
        if (intervalTasks[taskId]) clearInterval(intervalTasks[taskId]);
        delete intervalTasks[taskId];

        // 移除任务 + 保存
        tasks.splice(taskIndex, 1);
        saveTasks(tasks);
        updateTaskList();
        console.log(`[网易云定时] 已取消每日${tasks[taskIndex]?.time}的${tasks[taskIndex]?.action}任务`);
    }

    // 3. 取消所有每日任务
    function cancelAllDailyTasks() {
        const tasks = loadTasks();
        // 清除所有定时器
        tasks.forEach(task => {
            clearTimeout(task.firstTimerId);
            if (intervalTasks[task.id]) clearInterval(intervalTasks[task.id]);
        });
        intervalTasks = {};
        // 清空存储
        saveTasks([]);
        updateTaskList();
        alert('❌ 已取消所有每日重复任务');
    }

    // ========== UI面板(管理每日重复任务) ==========
    function updateTaskList() {
        const listEl = document.getElementById('taskList');
        if (!listEl) return;

        const tasks = loadTasks();
        listEl.innerHTML = '';

        if (tasks.length === 0) {
            listEl.innerHTML = '<div style="color:#999; padding:5px;">暂无每日重复任务</div>';
            return;
        }

        // 渲染所有重复任务
        tasks.forEach(task => {
            const item = document.createElement('div');
            item.style.cssText = 'padding:4px 0; border-bottom:1px solid #eee; display:flex; justify-content:space-between; align-items:center;';
            item.innerHTML = `
                <span>🔁 每天${task.time}:自动${task.action}音乐</span>
                <button class="cancelTaskBtn" data-id="${task.id}" style="padding:2px 6px; background:#f5222d; color:#fff; border:none; border-radius:3px; cursor:pointer;">取消</button>
            `;
            listEl.appendChild(item);

            // 绑定取消事件
            item.querySelector('.cancelTaskBtn').addEventListener('click', (e) => {
                cancelDailyTask(e.target.dataset.id);
            });
        });
    }

    // 创建UI面板
    function createUIPanel() {
        const panel = document.createElement('div');
        panel.style.cssText = `
            position: fixed; top: 20px; right: 20px; z-index: 99999;
            background: #fff; padding: 15px; border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            font-size: 14px; color: #333; min-width: 300px;
        `;
        panel.innerHTML = `
            <div style="margin-bottom: 10px; font-weight: bold; font-size:16px;">网易云每日定时</div>
            <!-- 时间设置区域 -->
            <div style="margin-bottom: 10px; padding-bottom:10px; border-bottom:1px solid #eee;">
                <div style="margin-bottom: 8px; display:flex; align-items:center; gap:8px;">
                    <label>每日执行时间:</label>
                    <input type="number" id="taskHour" placeholder="小时(0-23)" min="0" max="23" style="padding:4px; width:60px;">
                    <span>:</span>
                    <input type="number" id="taskMinute" placeholder="分钟(0-59)" min="0" max="59" style="padding:4px; width:60px;">
                </div>
                <div style="margin-bottom: 8px;">
                    <label>执行操作:</label>
                    <select id="taskAction" style="padding:4px; width:120px;">
                        <option value="play">自动播放</option>
                        <option value="pause">自动暂停</option>
                    </select>
                </div>
                <button id="addTaskBtn" style="padding:4px 12px; background:#1890ff; color:#fff; border:none; border-radius:4px; cursor:pointer;">添加每日任务</button>
            </div>
            <!-- 任务列表区域 -->
            <div>
                <div style="margin-bottom:8px; font-weight:bold;">已添加的每日任务:</div>
                <div id="taskList" style="max-height:200px; overflow-y:auto; padding:5px; border:1px solid #eee; border-radius:4px;">
                    <div style="color:#999; padding:5px;">暂无每日重复任务</div>
                </div>
                <button id="cancelAllBtn" style="margin-top:8px; padding:4px 12px; background:#666; color:#fff; border:none; border-radius:4px; cursor:pointer;">取消所有任务</button>
            </div>
        `;
        document.body.appendChild(panel);

        // 绑定添加任务事件
        document.getElementById('addTaskBtn').addEventListener('click', () => {
            const hour = Number(document.getElementById('taskHour').value);
            const minute = Number(document.getElementById('taskMinute').value);
            const action = document.getElementById('taskAction').value;

            if (isNaN(hour) || isNaN(minute)) {
                alert('请输入有效的小时和分钟!');
                return;
            }

            addDailyTask(hour, minute, action);
            // 清空输入框
            document.getElementById('taskHour').value = '';
            document.getElementById('taskMinute').value = '';
        });

        // 绑定取消所有任务事件
        document.getElementById('cancelAllBtn').addEventListener('click', cancelAllDailyTasks);
    }

    // ========== 初始化:加载任务+创建UI ==========
    function init() {
        // 1. 创建UI面板
        createUIPanel();
        // 2. 加载本地存储的任务,重建定时器
        const tasks = loadTasks();
        tasks.forEach(task => {
            const [hour, minute] = task.time.split(':').map(Number);
            const action = task.action === '播放' ? 'play' : 'pause';
            const { firstDelay, repeatInterval } = getDelayAndInterval(hour, minute);

            // 重建首次执行+重复定时器
            const firstTimer = setTimeout(() => {
                togglePlayPause(action);
                const intervalId = setInterval(() => {
                    togglePlayPause(action);
                }, repeatInterval);
                intervalTasks[task.id] = intervalId;
            }, firstDelay);

            // 更新任务的首次定时器ID(防止页面刷新后无法取消)
            task.firstTimerId = firstTimer;
        });
        saveTasks(tasks);
        // 3. 更新任务列表UI
        updateTaskList();
    }

    // 页面加载完成后初始化
    if (document.readyState === 'complete') {
        init();
    } else {
        window.addEventListener('load', init);
    }

})();

效果图:

飞牛也可以用应用市场的浏览器,方便在外面访问,但是谷歌安装油猴插件有点麻烦


可以在电脑保存好脚本,通过 vnc 的控制栏上传文件


📌 转载信息
原作者:
shuffle
转载时间:
2025/12/25 10:39:32