您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
B站视频/番剧自动点赞。强化模块化设计,完善错误处理,优化选择器策略
// ==UserScript== // @name B站自动点赞 (模块化增强版) // @namespace http://tampermonkey.net/ // @version 2025.06.21 // @description B站视频/番剧自动点赞。强化模块化设计,完善错误处理,优化选择器策略 // @author Gemini (重构优化) // @match https://www.bilibili.com/video/* // @match https://www.bilibili.com/bangumi/play/* // @icon https://www.bilibili.com/favicon.ico // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // ------------------------- // 1. 核心配置模块 // ------------------------- const CONFIG = { // 触发点赞的条件配置 PLAY_PROGRESS_THRESHOLD: 30, // 播放进度百分比阈值 PLAY_TIME_THRESHOLD: 60, // 播放时间阈值(秒) // 运行时控制参数 CHECK_INTERVAL: 1500, // 状态检查间隔(毫秒) INIT_DELAY: 1500, // 初始化延迟(毫秒) ACTION_DELAY: 200, // 操作反馈延迟(毫秒) POST_ACTION_DELAY: 700, // 操作后检查延迟(毫秒) // 调试与日志配置 DEBUG_MODE: true, // 调试模式开关 LOG_LEVEL: 'INFO' // 日志级别: DEBUG/INFO/WARN/ERROR }; // ------------------------- // 2. 全局状态管理 // ------------------------- let state = { checkInterval: null, // 进度检查定时器ID currentUrl: '', // 当前页面URL isLiked: false, // 当前点赞状态 pageType: 'unknown' // 页面类型标识 }; // ------------------------- // 3. 日志系统模块 // ------------------------- /** * 统一日志输出接口 * @param {string} level - 日志级别: DEBUG/INFO/WARN/ERROR * @param {string} message - 日志内容 */ function log(level, message) { if (!CONFIG.DEBUG_MODE && level !== 'ERROR') return; const levels = { DEBUG: '\x1b[36m[DEBUG]\x1b[0m', INFO: '\x1b[32m[INFO]\x1b[0m', WARN: '\x1b[33m[WARN]\x1b[0m', ERROR: '\x1b[31m[ERROR]\x1b[0m' }; if (levels[level]) { console.log(`[BiliAutoLike] ${new Date().toLocaleTimeString()} ${levels[level]} - ${message}`); } } // ------------------------- // 4. 页面类型识别模块 // ------------------------- const PAGE_TYPES = { VIDEO: 'video', BANGUMI: 'bangumi', UNKNOWN: 'unknown' }; /** * 获取当前页面类型 * @returns {string} 页面类型常量 */ function getPageType() { const href = window.location.href; const isBangumi = href.includes('/bangumi/play/'); const isVideo = href.includes('/video/'); state.pageType = isBangumi ? PAGE_TYPES.BANGUMI : isVideo ? PAGE_TYPES.VIDEO : PAGE_TYPES.UNKNOWN; log('INFO', `识别页面类型: ${state.pageType}`); return state.pageType; } // ------------------------- // 5. 选择器策略管理模块 // ------------------------- /** * 点赞按钮选择器策略集合 * 按优先级顺序排列选择器 */ const likeButtonStrategies = { [PAGE_TYPES.VIDEO]: [ '.bpx-player-ctrl .bpx-player-ctrl-like', // 新版播放器点赞按钮 'div[title="点赞(Q)"]', // 标题属性选择器 '.video-toolbar-left-item.video-like', // 视频工具栏点赞 'button[aria-label="点赞"]' // 无障碍属性选择器 ], [PAGE_TYPES.BANGUMI]: [ '.ep-handle-like', // 新版番剧点赞按钮 '.bangumi-player-container .like-btn', // 番剧播放器内按钮 '#like_info', // 旧版ID选择器 'button[class*="bangumi-player-like"]' // 类名模糊匹配 ], [PAGE_TYPES.UNKNOWN]: [ 'button[aria-label="点赞"]', // 通用无障碍选择器 '.like-button', // 通用类名选择器 '.bpx-player-ctrl-like' // 尝试通用播放器选择器 ] }; /** * 获取点赞按钮元素 * @returns {HTMLElement|null} 点赞按钮元素或null */ function getLikeButton() { const pageType = getPageType(); const selectors = likeButtonStrategies[pageType] || likeButtonStrategies[PAGE_TYPES.UNKNOWN]; log('DEBUG', `为${pageType}页面查找点赞按钮,尝试选择器: ${selectors.join(', ')}`); for (const selector of selectors) { const element = document.querySelector(selector); if (element && isElementVisible(element)) { log('INFO', `找到点赞按钮,选择器: ${selector}`); return element; } } log('WARN', `未找到可见的点赞按钮,页面类型: ${pageType}`); return null; } // ------------------------- // 6. DOM状态检测模块 // ------------------------- /** * 检测元素是否可见 * @param {HTMLElement} element 目标元素 * @returns {boolean} 元素可见性状态 */ function isElementVisible(element) { if (!element) return false; const style = window.getComputedStyle(element); const rect = element.getBoundingClientRect(); return ( style.display !== 'none' && style.visibility !== 'hidden' && style.opacity > 0 && rect.height > 0 && rect.width > 0 ); } /** * 检测视频是否已点赞 * @param {HTMLElement} button 点赞按钮元素 * @returns {boolean} 点赞状态 */ function isVideoLiked(button) { if (!button) return true; // 找不到按钮时假设已点赞避免重复操作 const activeStates = [ 'on', 'actived', 'is-liked', 'liked', 'active', 'zaned', 'bili-__heart-on', 'bili-__heart-active' // 补充B站新状态类名 ]; for (const stateClass of activeStates) { if (button.classList.contains(stateClass)) { log('DEBUG', `检测到已点赞状态类: ${stateClass}`); return true; } } // 检测无障碍属性 if (button.getAttribute('aria-checked') === 'true' || button.getAttribute('aria-pressed') === 'true') { log('DEBUG', '检测到已点赞无障碍属性'); return true; } // 检测子元素状态 const likedIndicators = [ '.icon-liked', '.bili-__icon-heart-fill', '[class*="icon_like_selected"]', '.liked-icon' ]; for (const selector of likedIndicators) { if (button.querySelector(selector)) { log('DEBUG', `子元素检测到点赞状态: ${selector}`); return true; } } return false; } // ------------------------- // 7. 点赞操作执行模块 // ------------------------- /** * 执行点赞操作 * @param {HTMLElement} button 点赞按钮元素 */ function likeVideo(button) { if (isVideoLiked(button)) { log('INFO', '目标已点赞,跳过操作'); stopProgressCheck(); return; } log('INFO', '开始执行点赞操作'); const pageType = getPageType(); const strategy = getClickStrategy(pageType); try { // 添加视觉反馈 button.style.transition = 'transform 0.15s ease'; button.style.transform = 'scale(1.2)'; setTimeout(() => { // 执行对应页面的点击策略 strategy(button); // 恢复按钮样式 button.style.transform = ''; log('INFO', '点赞操作事件已派发'); // 延迟检查点赞结果 setTimeout(checkLikeResult, CONFIG.POST_ACTION_DELAY); }, CONFIG.ACTION_DELAY); } catch (error) { log('ERROR', `点赞操作异常: ${error.message}`); button.style.transform = ''; stopProgressCheck(); } } /** * 获取点击策略函数 * @param {string} pageType 页面类型 * @returns {Function} 点击策略函数 */ function getClickStrategy(pageType) { switch (pageType) { case PAGE_TYPES.VIDEO: return videoClickStrategy; case PAGE_TYPES.BANGUMI: return bangumiClickStrategy; default: return defaultClickStrategy; } } /** * 普通视频点击策略 * @param {HTMLElement} button 点赞按钮 */ function videoClickStrategy(button) { log('DEBUG', '执行视频页面点击策略: mouseover -> mousedown -> mouseup -> click'); dispatchEvents(button, ['mouseover', 'mousedown', 'mouseup', 'click']); } /** * 番剧页面点击策略 * @param {HTMLElement} button 点赞按钮 */ function bangumiClickStrategy(button) { log('DEBUG', '执行番剧页面点击策略: mousedown -> mouseup'); dispatchEvents(button, ['mousedown', 'mouseup']); } /** * 默认点击策略 * @param {HTMLElement} button 点赞按钮 */ function defaultClickStrategy(button) { log('DEBUG', '执行默认点击策略: 直接click()'); button.click(); } /** * 派发事件序列 * @param {HTMLElement} element 目标元素 * @param {string[]} eventTypes 事件类型数组 */ function dispatchEvents(element, eventTypes) { eventTypes.forEach(type => { const event = new MouseEvent(type, { bubbles: true, cancelable: true, view: window }); log('DEBUG', `派发事件: ${type}`); element.dispatchEvent(event); }); } /** * 检查点赞结果 */ function checkLikeResult() { const button = getLikeButton(); if (button && isVideoLiked(button)) { log('INFO', '点赞成功,按钮状态已更新'); } else { log('WARN', '点赞可能未成功,按钮状态未改变'); log('DEBUG', '建议检查选择器是否匹配当前页面版本'); } stopProgressCheck(); } // ------------------------- // 8. 播放进度监控模块 // ------------------------- /** * 检查播放进度并触发点赞 */ function checkPlayProgress() { const video = document.querySelector('video'); if (!video) { log('WARN', '未找到视频播放元素'); return; } // 跳过未准备好的视频 if (video.paused || video.ended || video.readyState < 2) { return; } const currentTime = video.currentTime; const duration = video.duration; // 过滤无效视频 if (!duration || duration === Infinity || duration < 5) { log('DEBUG', '视频时长无效或过短'); return; } const progressPercent = (currentTime / duration) * 100; const meetProgress = progressPercent >= CONFIG.PLAY_PROGRESS_THRESHOLD; const meetTime = currentTime >= CONFIG.PLAY_TIME_THRESHOLD; if (meetProgress || meetTime) { log('INFO', `满足点赞条件 - 进度: ${progressPercent.toFixed(1)}% (阈值: ${CONFIG.PLAY_PROGRESS_THRESHOLD}%) ` + `或 时间: ${currentTime.toFixed(1)}s (阈值: ${CONFIG.PLAY_TIME_THRESHOLD}s)`); const button = getLikeButton(); if (button) { likeVideo(button); } else { log('WARN', '满足点赞条件但未找到点赞按钮,停止检查'); stopProgressCheck(); } } } /** * 启动进度检查 */ function startProgressCheck() { if (state.checkInterval) return; log('INFO', `启动播放进度检查,间隔: ${CONFIG.CHECK_INTERVAL}ms`); state.checkInterval = setInterval(checkPlayProgress, CONFIG.CHECK_INTERVAL); } /** * 停止进度检查 */ function stopProgressCheck() { if (state.checkInterval) { clearInterval(state.checkInterval); state.checkInterval = null; log('INFO', '停止播放进度检查'); } } // ------------------------- // 9. 脚本生命周期管理模块 // ------------------------- /** * 初始化脚本模块 */ function initialize() { stopProgressCheck(); const video = document.querySelector('video'); const button = getLikeButton(); if (video && button) { log('INFO', '脚本初始化完成,视频元素和点赞按钮已就绪'); if (isVideoLiked(button)) { log('INFO', '目标已点赞,无需启动检查'); return; } startProgressCheck(); } else { log('WARN', '初始化失败,缺少关键元素'); if (!video) log('DEBUG', '未找到<video>元素'); if (!button) log('DEBUG', '未找到点赞按钮'); } } /** * 主执行函数 */ function main() { // 检测URL变化 if (window.location.href === state.currentUrl && state.checkInterval) { log('DEBUG', 'URL未变化,跳过重复初始化'); return; } state.currentUrl = window.location.href; log('INFO', `脚本启动/页面变更 - URL: ${state.currentUrl}, 类型: ${getPageType()}`); stopProgressCheck(); // 延迟初始化,等待页面资源加载 setTimeout(initialize, CONFIG.INIT_DELAY); } // ------------------------- // 10. 启动脚本 // ------------------------- log('INFO', 'B站自动点赞脚本(模块化增强版)已加载,等待页面初始化...'); // 页面加载完成事件 window.addEventListener('load', main); // 监听SPA路由变化 const originalPushState = history.pushState; history.pushState = function() { const result = originalPushState.apply(this, arguments); log('DEBUG', '检测到history.pushState,重新初始化脚本'); main(); return result; }; const originalReplaceState = history.replaceState; history.replaceState = function() { const result = originalReplaceState.apply(this, arguments); log('DEBUG', '检测到history.replaceState,重新初始化脚本'); main(); return result; }; // DOM变化观察器 const observer = new MutationObserver(mutations => { if (!state.checkInterval) { log('DEBUG', 'DOM变化检测,尝试初始化脚本'); initialize(); } }); observer.observe(document.body, { childList: true, subtree: true, attributes: true }); // 处理文档已加载情况 if (document.readyState === 'complete' || document.readyState === 'interactive') { main(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址