介绍

控制面板在浏览器右下角的巨大❤
QQ20260904-131004.png
ScreenShot_2026-09-04_131016_576.png
设定好运行持续时间点击开始就可以自动点赞了

源码

// ==UserScript==
// @name         B站直播间自动点赞
// @namespace    http://tampermonkey.net/
// @version      2.6
// @description  B站直播间自动点赞
// @author       海问香
// @match        https://live.bilibili.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // ---------- 全局唯一标记,防止脚本重复加载 ----------
    if (window.__BILI_AUTO_LIKE_INSTALLED__) {
        console.log('⏭️ 脚本已安装,跳过重复初始化');
        return;
    }
    window.__BILI_AUTO_LIKE_INSTALLED__ = true;

    // ---------- 仅在顶层窗口执行 ----------
    if (window.top !== window.self) {
        console.log('⏭️ 当前在 iframe 中,跳过创建面板');
        return;
    }

    // ---------- 状态变量 ----------
    let isRunning = false;
    let count = 0;
    let timer = null;
    let stopTimeout = null;
    let isExpanded = false;

    // ---------- DOM 引用 ----------
    let toggleBtn = null;
    let panel = null;
    let statusText = null;
    let clickCountSpan = null;
    let actionBtn = null;
    let durationInput = null;
    let modeRadios = null;
    let testBtn = null;

    // ---------- 工具函数:跨 iframe 查找元素 ----------
    function findElementInAllFrames(selector) {
        let el = document.querySelector(selector);
        if (el) return el;
        const iframes = document.querySelectorAll('iframe');
        for (let iframe of iframes) {
            try {
                const doc = iframe.contentDocument || iframe.contentWindow.document;
                if (doc) {
                    el = doc.querySelector(selector);
                    if (el) return el;
                }
            } catch (e) { /* 跨域忽略 */ }
        }
        return null;
    }

    // ---------- 获取点赞按钮(带等待) ----------
    function getLikeButton() {
        const selectors = [
            'div.like-btn[data-type="dianzan.0.show"]',
            '.like-btn[data-type="dianzan.0.show"]',
            '.like-btn',
            '[data-type="dianzan.0.show"]',
            '.bili-live-like-btn'
        ];
        for (let sel of selectors) {
            const btn = findElementInAllFrames(sel);
            if (btn) {
                console.log(`✅ 找到点赞按钮(选择器:${sel})`, btn);
                return btn;
            }
        }
        console.warn('⚠️ 未找到点赞按钮');
        return null;
    }

    // ---------- 等待按钮出现(无限等待) ----------
    function waitForButton() {
        return new Promise((resolve) => {
            const check = () => {
                const btn = getLikeButton();
                if (btn) {
                    resolve(btn);
                } else {
                    setTimeout(check, 500);
                }
            };
            check();
        });
    }

    // ---------- 执行点击 ----------
    async function doClick() {
        const btn = await waitForButton();
        if (btn) {
            ['mousedown', 'mouseup', 'click'].forEach(type => {
                const ev = new MouseEvent(type, {
                    bubbles: true,
                    cancelable: true,
                    view: window
                });
                btn.dispatchEvent(ev);
            });
            btn.click();
            count++;
            updatePanelStatus();
            console.log(`👍 第 ${count} 次点击 (按钮)`);
        } else {
            // 备用键盘
            const event = new KeyboardEvent('keydown', { key: 'k', bubbles: true });
            document.dispatchEvent(event);
            count++;
            console.log(`👍 第 ${count} 次点击 (键盘 K)`);
            updatePanelStatus();
        }
    }

    // ---------- 测试点击 ----------
    async function testClick() {
        console.log('🔍 正在查找点赞按钮...');
        const btn = await waitForButton();
        if (btn) {
            console.log('✅ 找到按钮,正在模拟点击...');
            ['mousedown', 'mouseup', 'click'].forEach(type => {
                const ev = new MouseEvent(type, {
                    bubbles: true,
                    cancelable: true,
                    view: window
                });
                btn.dispatchEvent(ev);
            });
            btn.click();
            console.log('✅ 点击完成,请观察点赞数是否增加');
        } else {
            console.error('❌ 未找到点赞按钮');
        }
    }

    // ---------- 调度下一次点击 ----------
    function scheduleNext() {
        if (!isRunning) return;
        const mode = document.querySelector('input[name="mode"]:checked');
        let delay = (mode && mode.value === 'fast') ? 0.5 + Math.random() * 0.5 : 2 + Math.random() * 3;
        timer = setTimeout(() => {
            doClick().then(() => scheduleNext());
        }, delay * 1000);
    }

    // ---------- 启动/停止 ----------
    function startAuto() {
        if (isRunning) return;
        const durVal = parseFloat(durationInput.value);
        if (isNaN(durVal) || durVal <= 0) {
            alert('请输入有效的持续时间(分钟)');
            return;
        }
        count = 0;
        isRunning = true;
        updatePanelStatus();
        doClick().then(() => scheduleNext());
        if (stopTimeout) clearTimeout(stopTimeout);
        stopTimeout = setTimeout(() => {
            if (isRunning) {
                console.log(`⏰ 已运行 ${durVal} 分钟,自动停止`);
                stopAuto();
            }
        }, durVal * 60 * 1000);
        actionBtn.textContent = '停止点赞';
        actionBtn.style.backgroundColor = '#f44336';
        console.log(`▶️ 自动点赞已启动(模式:${getModeText()},持续 ${durVal} 分钟)`);
    }

    function stopAuto() {
        if (!isRunning) return;
        isRunning = false;
        if (timer) { clearTimeout(timer); timer = null; }
        if (stopTimeout) { clearTimeout(stopTimeout); stopTimeout = null; }
        updatePanelStatus();
        if (actionBtn) {
            actionBtn.textContent = '开始点赞';
            actionBtn.style.backgroundColor = '#4CAF50';
        }
        console.log(`⏹️ 自动点赞已停止,共点击 ${count} 次`);
    }

    function toggleAction() {
        isRunning ? stopAuto() : startAuto();
        collapsePanel();
    }

    function getModeText() {
        const mode = document.querySelector('input[name="mode"]:checked');
        return mode ? mode.value : 'slow';
    }

    function updatePanelStatus() {
        if (statusText) {
            statusText.textContent = isRunning ? '🟢 运行中' : '⏹ 已停止';
            statusText.style.color = isRunning ? '#4CAF50' : '#aaa';
        }
        if (clickCountSpan) clickCountSpan.textContent = count + ' 次';
        if (toggleBtn) {
            toggleBtn.style.borderColor = isRunning ? '#4CAF50' : '#888';
            toggleBtn.style.boxShadow = isRunning ? '0 0 10px rgba(76,175,80,0.6)' : 'none';
        }
    }

    // ---------- 面板折叠/展开 ----------
    function expandPanel() {
        if (isExpanded) return;
        isExpanded = true;
        toggleBtn.style.display = 'none';
        panel.style.display = 'block';
    }

    function collapsePanel() {
        if (!isExpanded) return;
        isExpanded = false;
        panel.style.display = 'none';
        toggleBtn.style.display = 'flex';
        updatePanelStatus();
    }

    // ---------- 创建UI(防重入 + 强力清理) ----------
    function createUI() {
        // 防止并发创建
        if (window.__BILI_CREATING_PANEL) {
            console.warn('⚠️ 已有创建进程,跳过本次');
            return;
        }
        window.__BILI_CREATING_PANEL = true;

        // 停止所有正在运行的任务
        if (isRunning) {
            isRunning = false;
            if (timer) { clearTimeout(timer); timer = null; }
            if (stopTimeout) { clearTimeout(stopTimeout); stopTimeout = null; }
        }

        // 强力清理:移除所有可能残留的容器(按多种特征)
        const candidates = document.querySelectorAll('[id*="auto-like"], [id*="bili-auto"], .auto-like-panel, [id="like-toggle"]');
        candidates.forEach(el => {
            // 避免误删其他元素,仅移除我们的容器及其父容器(如果父容器是 #auto-like-container)
            if (el.id === 'auto-like-container' || el.id === 'like-toggle' || el.closest('#auto-like-container')) {
                el.remove();
            }
        });
        // 专门移除 #auto-like-container(包括可能嵌套的)
        while (document.getElementById('auto-like-container')) {
            document.getElementById('auto-like-container').remove();
        }

        // 重置引用
        toggleBtn = panel = statusText = clickCountSpan = actionBtn = durationInput = modeRadios = testBtn = null;
        count = 0;
        isExpanded = false;

        // ---------- 创建新容器 ----------
        const container = document.createElement('div');
        container.id = 'auto-like-container';
        container.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 9999;
            font-family: 'Microsoft YaHei', sans-serif;
            font-size: 14px;
        `;

        // 折叠爱心
        const toggle = document.createElement('div');
        toggle.id = 'like-toggle';
        toggle.style.cssText = `
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background: #2d2d2d;
            color: #ff6b6b;
            font-size: 28px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            border: 2px solid #888;
            box-shadow: 0 4px 12px rgba(0,0,0,0.4);
            transition: all 0.2s;
            user-select: none;
        `;
        toggle.textContent = '❤️';
        toggle.title = '点击展开控制面板';
        toggle.addEventListener('click', expandPanel);
        container.appendChild(toggle);
        toggleBtn = toggle;

        // 展开面板
        const panelDiv = document.createElement('div');
        panelDiv.id = 'like-panel';
        panelDiv.style.cssText = `
            background: #2d2d2d;
            color: #eee;
            padding: 15px 20px;
            border-radius: 10px;
            width: 280px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.6);
            border: 1px solid #555;
            display: none;
        `;

        // 标题栏
        const header = document.createElement('div');
        header.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;';
        const title = document.createElement('span');
        title.style.cssText = 'font-weight: bold; font-size: 16px;';
        title.textContent = '🤖 自动点赞 v2.6';
        const close = document.createElement('span');
        close.textContent = '✕';
        close.style.cssText = 'cursor: pointer; font-size: 18px; color: #aaa; transition: 0.2s;';
        close.addEventListener('mouseenter', () => close.style.color = '#fff');
        close.addEventListener('mouseleave', () => close.style.color = '#aaa');
        close.addEventListener('click', collapsePanel);
        header.appendChild(title);
        header.appendChild(close);

        // 状态
        const statusDiv = document.createElement('div');
        statusDiv.style.cssText = 'display: flex; justify-content: space-between; margin-bottom: 10px; font-size: 13px;';
        const status = document.createElement('span');
        status.id = 'panel-status';
        status.textContent = '⏹ 已停止';
        status.style.color = '#aaa';
        const countSpan = document.createElement('span');
        countSpan.id = 'panel-count';
        countSpan.textContent = '0 次';
        statusDiv.appendChild(status);
        statusDiv.appendChild(countSpan);
        statusText = status;
        clickCountSpan = countSpan;

        // 模式
        const modeDiv = document.createElement('div');
        modeDiv.style.cssText = 'margin-bottom: 8px;';
        modeDiv.innerHTML = `
            <label style="margin-right:10px;">模式:</label>
            <label style="margin-right:10px; cursor:pointer;">
                <input type="radio" name="mode" value="slow" checked> 慢速(2-5s)
            </label>
            <label style="cursor:pointer;">
                <input type="radio" name="mode" value="fast"> 快速(0.5-1s)
            </label>
        `;
        modeRadios = modeDiv.querySelectorAll('input[name="mode"]');

        // 持续时间
        const durationDiv = document.createElement('div');
        durationDiv.style.cssText = 'display: flex; align-items: center; margin-bottom: 12px;';
        const durLabel = document.createElement('label');
        durLabel.textContent = '持续时间(分钟):';
        durLabel.style.cssText = 'margin-right: 8px;';
        const durInput = document.createElement('input');
        durInput.type = 'number';
        durInput.value = '5';
        durInput.min = '0.5';
        durInput.step = '0.5';
        durInput.style.cssText = `
            width: 70px;
            padding: 4px 6px;
            background: #444;
            color: #fff;
            border: 1px solid #666;
            border-radius: 4px;
            font-size: 13px;
        `;
        durationDiv.appendChild(durLabel);
        durationDiv.appendChild(durInput);
        durationInput = durInput;

        // 操作按钮
        const action = document.createElement('button');
        action.id = 'action-btn';
        action.textContent = '开始点赞';
        action.style.cssText = `
            width: 100%;
            background: #4CAF50;
            border: none;
            color: #fff;
            padding: 8px 0;
            border-radius: 5px;
            cursor: pointer;
            font-size: 15px;
            font-weight: bold;
            transition: 0.2s;
            margin-bottom: 6px;
        `;
        action.addEventListener('click', toggleAction);
        actionBtn = action;

        // 测试按钮
        const test = document.createElement('button');
        test.textContent = '🔍 测试点击';
        test.style.cssText = `
            width: 100%;
            background: #ff9800;
            border: none;
            color: #fff;
            padding: 5px 0;
            border-radius: 5px;
            cursor: pointer;
            font-size: 13px;
            transition: 0.2s;
        `;
        test.addEventListener('click', testClick);
        testBtn = test;

        panelDiv.appendChild(header);
        panelDiv.appendChild(statusDiv);
        panelDiv.appendChild(modeDiv);
        panelDiv.appendChild(durationDiv);
        panelDiv.appendChild(action);
        panelDiv.appendChild(test);
        container.appendChild(panelDiv);
        panel = panelDiv;

        document.body.appendChild(container);

        collapsePanel();

        // 释放创建锁
        window.__BILI_CREATING_PANEL = false;

        // 二次清理:延迟执行,确保没有多余的面板
        setTimeout(() => {
            const all = document.querySelectorAll('#auto-like-container');
            if (all.length > 1) {
                all.forEach((el, idx) => { if (idx > 0) el.remove(); });
                console.log('🧹 二次清理,移除了多余面板');
            }
        }, 300);

        console.log('✅ 自动点赞面板已加载(防重入版 v2.6)');
        console.log('💡 面板右下角爱心可展开,点击“测试点击”可验证功能');
    }

    // ---------- 初始化 ----------
    function init() {
        if (document.body) {
            createUI();
        } else {
            document.addEventListener('DOMContentLoaded', createUI);
        }
    }

    init();

    window.stopBiliLike = stopAuto;
    console.log('💡 手动停止:输入 stopBiliLike()');
})();