// ==UserScript== // @name B站自动连播控制助手 // @namespace https://space.bilibili.com/398910090 // @version 1.2 // @description 合集自动开启自动连播,单个视频自动禁用自动连播,支持特定合集设置 // @author Ace // @match https://www.bilibili.com/video/* // @icon https://www.bilibili.com/favicon.ico // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // ==/UserScript== (function() { 'use strict'; // 设置项:特定合集列表 const SETTING_KEY = 'bilibili_auto_play_settings'; // 获取设置 function getSettings() { return GM_getValue(SETTING_KEY, { enabledCollections: [], // 启用自动连播的特定合集 hotkeys: { toggleAutoPlay: 'Ctrl+Shift+L', // 切换连播状态快捷键 toggleCollection: 'Ctrl+Shift+A' // 快捷指定当前合集快捷键 }, showInfobar: true, // 是否显示切换成功提示 autoEnableNonAdded: true, // 选择不添加到列表时是否自动开启连播 autoDisableNewCollections: true // 新合集是否自动关闭连播 }); } // 保存设置 function saveSettings(settings) { GM_setValue(SETTING_KEY, settings); } // 添加特定合集 function addCollection(collectionId) { const settings = getSettings(); if (!settings.enabledCollections.includes(collectionId)) { settings.enabledCollections.push(collectionId); saveSettings(settings); showInfobar(`已添加合集 ${collectionId} 到自动连播列表`); } else { showInfobar(`合集 ${collectionId} 已在列表中`); } } // 移除特定合集 function removeCollection(collectionId) { const settings = getSettings(); const index = settings.enabledCollections.indexOf(collectionId); if (index > -1) { settings.enabledCollections.splice(index, 1); saveSettings(settings); // 获取当前视频的合集ID和视频ID const currentCollectionId = getCollectionId(); const currentVideoId = getVideoIdFromUrl(); // 如果移除的是当前视频的合集或视频ID,启用手动控制 if (currentCollectionId === collectionId || currentVideoId === collectionId) { enableManualControl(); // 根据设置决定连播状态 if (settings.autoEnableNonAdded !== false) { setAutoPlayState(true); } else { setAutoPlayState(false); } } showInfobar(`已从自动连播列表移除合集 ${collectionId}`); } else { showInfobar(`合集 ${collectionId} 不在列表中`); } } // 查看设置 function viewSettings() { const settings = getSettings(); const collections = settings.enabledCollections.length > 0 ? settings.enabledCollections.join('\n') : '暂无特定合集设置'; showInfobar(`自动连播合集列表:\n${collections}`); } // 注册菜单命令 GM_registerMenuCommand('添加当前合集到自动连播', function() { // 获取合集ID const collectionId = getCollectionId(); if (collectionId) { addCollection(collectionId); } else { // 如果没有合集ID,使用视频ID作为备选 const videoId = getVideoIdFromUrl(); if (videoId) { addCollection(videoId); } else { showInfobar('当前页面不是B站视频页面'); } } }); GM_registerMenuCommand('从自动连播移除当前合集', function() { // 获取合集ID const collectionId = getCollectionId(); if (collectionId) { removeCollection(collectionId); } else { // 如果没有合集ID,使用视频ID作为备选 const videoId = getVideoIdFromUrl(); if (videoId) { removeCollection(videoId); } else { showInfobar('当前页面不是B站视频页面'); } } }); GM_registerMenuCommand('查看自动连播助手设置', viewSettings); // 从URL获取视频标识(支持AV号和BV号) function getVideoIdFromUrl() { // 匹配BV号:BV开头,后跟大小写字母和数字,共12位 const bvMatch = window.location.href.match(/BV([A-Za-z0-9]{10,})/); if (bvMatch) return bvMatch[1]; // 匹配AV号:av开头,后跟数字 const avMatch = window.location.href.match(/av(\d+)/); if (avMatch) return avMatch[1]; return null; } // 获取合集唯一ID(sid) function getCollectionId() { // 尝试从页面中提取合集sid const collectionLink = document.querySelector('.video-pod__header .header-top .left .title.jumpable'); if (collectionLink) { const href = collectionLink.getAttribute('href'); if (href) { const sidMatch = href.match(/sid=(\d+)/); if (sidMatch) return sidMatch[1]; } } // 如果无法从页面获取,尝试从URL获取 const sidFromUrl = window.location.href.match(/sid=(\d+)/); if (sidFromUrl) return sidFromUrl[1]; return null; } // 检查是否为合集页面 function isCollectionPage() { // 合集页面特征:包含"自动连播"文字且是合集相关的按钮 const autoPlayBtn = document.querySelector('.continuous-btn'); if (!autoPlayBtn) return false; // 检查按钮是否包含"自动连播"文字 const autoPlayText = autoPlayBtn.querySelector('.txt'); if (!autoPlayText || autoPlayText.textContent !== '自动连播') return false; // 检查按钮父级结构,确保是合集页面的控制 return autoPlayBtn.closest('.video-pod') !== null; } // 检查当前视频是否在启用列表中 function isInEnabledList() { // 优先使用合集ID const collectionId = getCollectionId(); if (collectionId) { const settings = getSettings(); return settings.enabledCollections.includes(collectionId); } // 兼容处理:如果没有合集ID,使用视频ID const videoId = getVideoIdFromUrl(); if (!videoId) return false; const settings = getSettings(); return settings.enabledCollections.includes(videoId); } // 设置自动连播状态 function setAutoPlayState(enabled) { // 检查设置界面是否打开,如果打开则不执行任何操作 const settingsOverlay = document.getElementById('bilibili-auto-play-settings-overlay'); if (settingsOverlay && settingsOverlay.style.display === 'block') { return false; } const continuousBtn = document.querySelector('.continuous-btn'); const switchBtn = document.querySelector('.continuous-btn .switch-btn'); if (!switchBtn || !continuousBtn) return false; const isCurrentlyEnabled = switchBtn.classList.contains('on'); // 如果状态相同,无需修改 if (isCurrentlyEnabled === enabled) { // 但需要确保滑块位置正确,防止显示异常 const switchBlock = switchBtn.querySelector('.switch-block'); if (switchBlock) { if (enabled) { switchBlock.style.transform = 'none'; } else { switchBlock.style.transform = 'none'; } } return true; } // 如果状态不同,则进行切换 if (switchBtn.style.pointerEvents === 'none') { // 对于锁定状态的按钮,直接修改类名和滑块位置来反映正确状态 if (enabled) { switchBtn.classList.add('on'); } else { switchBtn.classList.remove('on'); } const switchBlock = switchBtn.querySelector('.switch-block'); if (switchBlock) { if (enabled) { switchBlock.style.transform = 'none'; } else { switchBlock.style.transform = 'none'; } } // 同时确保实际功能状态一致,通过直接调用B站的API continuousBtn.click(); return true; } else { // 对于未锁定的按钮,点击切换 const switchBlock = switchBtn.querySelector('.switch-block'); if (switchBlock) { switchBlock.click(); return true; } } return false; } // 观察页面变化 function observePageChanges() { const observer = new MutationObserver(() => { // 只有当连播按钮存在时才执行操作 const continuousBtn = document.querySelector('.continuous-btn'); if (continuousBtn) { // 检查是否为有效的视频页面 const videoId = getVideoIdFromUrl(); if (videoId) { const settings = getSettings(); if (isCollectionPage() && isInEnabledList()) { // 合集在启用列表中,强制开启连播并锁定 const switchBtn = continuousBtn.querySelector('.switch-btn'); if (switchBtn) { // 确保按钮处于开启状态 setAutoPlayState(true); // 如果按钮未锁定,禁用手动控制 if (switchBtn.style.pointerEvents !== 'none') { disableManualControl(); } } } else if (isCollectionPage()) { // 合集不在启用列表中 // 启用手动控制 enableManualControl(); // 检查是否需要自动关闭连播 if (settings.autoDisableNewCollections) { // 确保连播关闭 setAutoPlayState(false); } else if (settings.autoEnableNonAdded) { // 如果没有设置自动关闭新合集,但设置了不添加时自动开启连播 setAutoPlayState(true); } } else { // 单个视频,禁用连播 setAutoPlayState(false); // 启用手动控制 enableManualControl(); } } } }); // 只监听播放器区域的变化,而不是整个document.body const playerContainer = document.querySelector('.bpx-player'); if (playerContainer) { observer.observe(playerContainer, { childList: true, subtree: true }); } else { // 如果播放器容器不存在,回退到监听body,但添加延迟 observer.observe(document.body, { childList: true, subtree: true }); } } // 初始化 function init() { // 页面加载完成后执行 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { initLogic(); initButtonInjection(); // 注入设置按钮到播放器控制栏 }, 1000); // 延迟执行,确保页面元素加载完成 }); } else { setTimeout(() => { initLogic(); initButtonInjection(); // 注入设置按钮到播放器控制栏 }, 1000); } } // 初始化逻辑 function initLogic() { // 创建设置界面 createSettingsUI(); // 检查是否为合集页面 if (isCollectionPage()) { // 获取合集ID const collectionId = getCollectionId(); const settings = getSettings(); // 判断是否为新合集(不在列表中) const isNewCollection = !settings.enabledCollections.includes(collectionId); // 如果是新合集,且设置了自动关闭连播 if (isNewCollection && settings.autoDisableNewCollections) { // 立即关闭连播 setAutoPlayState(false); showInfobar('检测到新合集,已根据设置自动关闭连播'); } // 如果合集不在列表中,显示通知 if (isNewCollection) { showNotification( '检测到当前为合集页面,是否将其添加到自动连播列表?', { confirmText: '添加', cancelText: '不添加', autoClose: false, // 不自动关闭,必须用户选择 onConfirm: () => { // 添加到自动连播列表 addCollection(collectionId); // 立即开启连播 setAutoPlayState(true); // 禁用手动控制 disableManualControl(); showInfobar('已添加到自动连播列表,连播按钮已锁定'); }, onCancel: () => { const settings = getSettings(); // 根据设置决定是否自动开启连播 if (settings.autoEnableNonAdded !== false) { // 自动开启连播 setAutoPlayState(true); showInfobar('已自动开启连播,您可手动关闭'); } else { // 不自动开启连播 setAutoPlayState(false); showInfobar('未自动开启连播,您可手动开启'); } // 观察页面变化 observePageChanges(); } } ); } else { // 如果已经在列表中,自动开启连播并禁用手动控制 setAutoPlayState(true); disableManualControl(); } } else { // 单个视频,禁用连播 setAutoPlayState(false); } // 统一观察页面变化,不再根据条件判断 observePageChanges(); // 观察更多播放设置区域,注入连播设置入口 observeMoreSettingsArea(); // 注册快捷键监听 registerHotkeyListener(); } // 禁用连播按钮的手动控制 function disableManualControl() { const switchBtn = document.querySelector('.continuous-btn .switch-btn'); if (switchBtn) { // 确保连播已经开启 const isCurrentlyEnabled = switchBtn.classList.contains('on'); // 移除所有现有事件监听器 const newSwitchBtn = switchBtn.cloneNode(true); switchBtn.parentNode.replaceChild(newSwitchBtn, switchBtn); // 强制设置为开启状态,确保显示与实际一致 newSwitchBtn.classList.add('on'); const newSwitchBlock = newSwitchBtn.querySelector('.switch-block'); if (newSwitchBlock) { newSwitchBlock.style.transform = 'none'; // 确保滑块在正确位置 } // 阻止点击事件 newSwitchBtn.style.pointerEvents = 'none'; newSwitchBtn.style.opacity = '0.8'; // 直接调用B站的连播设置API,确保实际功能开启 // 这里通过模拟点击来确保实际功能开启 const continuousBtn = document.querySelector('.continuous-btn'); if (continuousBtn && !isCurrentlyEnabled) { continuousBtn.click(); } } } // 启用连播按钮的手动控制 function enableManualControl() { const switchBtn = document.querySelector('.continuous-btn .switch-btn'); if (switchBtn) { // 检查按钮是否被锁定 if (switchBtn.style.pointerEvents === 'none') { // 获取当前按钮状态 const isOn = switchBtn.classList.contains('on'); // 创建新按钮,保留原始按钮的HTML结构但不保留事件监听器 const newSwitchBtn = document.createElement('div'); newSwitchBtn.className = switchBtn.className; newSwitchBtn.innerHTML = switchBtn.innerHTML; // 恢复按钮状态 newSwitchBtn.style.pointerEvents = ''; newSwitchBtn.style.opacity = ''; // 确保开关状态和滑块位置正确 const newSwitchBlock = newSwitchBtn.querySelector('.switch-block'); if (newSwitchBlock) { if (isOn) { newSwitchBtn.classList.add('on'); newSwitchBlock.style.transform = 'none'; } else { newSwitchBtn.classList.remove('on'); newSwitchBlock.style.transform = 'none'; } } // 替换按钮 switchBtn.parentNode.replaceChild(newSwitchBtn, switchBtn); } } } // 显示设置界面 function showSettings() { loadSettingsToUI(); applyTheme(); document.getElementById('bilibili-auto-play-settings-overlay').style.display = 'block'; // 禁止背景滚动 document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; } // 隐藏设置界面 function hideSettings() { document.getElementById('bilibili-auto-play-settings-overlay').style.display = 'none'; // 恢复背景滚动 document.body.style.overflow = 'auto'; document.documentElement.style.overflow = 'auto'; } // 应用主题 function applyTheme() { const settingsPanel = document.getElementById('bilibili-auto-play-settings'); if (!settingsPanel) return; // 检测系统主题 const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; // 移除旧主题类 settingsPanel.classList.remove('bilibili-settings-light', 'bilibili-settings-dark'); // 添加新主题类 if (isDark) { settingsPanel.classList.add('bilibili-settings-dark'); } else { settingsPanel.classList.add('bilibili-settings-light'); } } // 加载设置到界面 function loadSettingsToUI() { const settings = getSettings(); // 添加防御性检查,确保设置结构完整 const hotkeys = settings.hotkeys || { toggleAutoPlay: 'Ctrl+Shift+L', toggleCollection: 'Ctrl+Shift+A' }; document.getElementById('toggleAutoPlay-hotkey').value = hotkeys.toggleAutoPlay; document.getElementById('toggleCollection-hotkey').value = hotkeys.toggleCollection; document.getElementById('show-infobar').checked = settings.showInfobar !== false; document.getElementById('auto-enable-non-added').checked = settings.autoEnableNonAdded !== false; document.getElementById('auto-disable-new-collections').checked = settings.autoDisableNewCollections === true; // 加载连播列表 updateCollectionList(); } // 保存界面设置 function saveSettingsFromUI() { const settings = getSettings(); // 确保hotkeys对象存在 if (!settings.hotkeys) { settings.hotkeys = { toggleAutoPlay: 'Ctrl+Shift+L', toggleCollection: 'Ctrl+Shift+A' }; } settings.hotkeys.toggleAutoPlay = document.getElementById('toggleAutoPlay-hotkey').value; settings.hotkeys.toggleCollection = document.getElementById('toggleCollection-hotkey').value; settings.showInfobar = document.getElementById('show-infobar').checked; settings.autoEnableNonAdded = document.getElementById('auto-enable-non-added').checked; settings.autoDisableNewCollections = document.getElementById('auto-disable-new-collections').checked; saveSettings(settings); // 保存后自动关闭设置界面 hideSettings(); showInfobar('设置已保存'); } // 更新连播列表 function updateCollectionList() { const settings = getSettings(); const collectionList = document.getElementById('collection-list'); const collectionCount = document.getElementById('collection-count'); if (!collectionList || !collectionCount) return; // 清空当前列表 collectionList.innerHTML = ''; if (settings.enabledCollections.length === 0) { // 显示空列表提示 collectionList.innerHTML = '
暂无已添加的合集
'; collectionCount.textContent = '0'; } else { // 显示合集列表 settings.enabledCollections.forEach(collectionId => { const collectionItem = document.createElement('div'); collectionItem.className = 'collection-item'; collectionItem.innerHTML = `
${collectionId}
`; collectionList.appendChild(collectionItem); }); collectionCount.textContent = settings.enabledCollections.length; // 添加删除按钮事件监听 document.querySelectorAll('.remove-collection-btn').forEach(btn => { btn.addEventListener('click', function() { const collectionId = this.getAttribute('data-id'); removeCollectionFromList(collectionId); }); }); } } // 从列表中移除合集 function removeCollectionFromList(collectionId) { const settings = getSettings(); settings.enabledCollections = settings.enabledCollections.filter(id => id !== collectionId); saveSettings(settings); // 如果移除的是当前视频的合集,启用手动控制 const currentVideoId = getVideoIdFromUrl(); if (currentVideoId === collectionId) { enableManualControl(); // 根据设置决定连播状态 if (settings.autoEnableNonAdded !== false) { setAutoPlayState(true); } else { setAutoPlayState(false); } } updateCollectionList(); showInfobar(`合集 ${collectionId} 已从连播列表中移除`); } // 清空连播列表 function clearCollectionList() { const settings = getSettings(); const currentVideoId = getVideoIdFromUrl(); const wasInList = settings.enabledCollections.includes(currentVideoId); settings.enabledCollections = []; saveSettings(settings); // 如果当前视频的合集在列表中,启用手动控制 if (wasInList) { enableManualControl(); // 根据设置决定连播状态 if (settings.autoEnableNonAdded !== false) { setAutoPlayState(true); } else { setAutoPlayState(false); } } updateCollectionList(); showInfobar('连播列表已清空,当前视频连播按钮已解锁'); } // 导出设置 function exportSettings() { const settings = getSettings(); const dataStr = JSON.stringify(settings, null, 2); const dataBlob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = 'bilibili_auto_play_settings.json'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showInfobar('设置已成功导出'); } // 导入设置 function importSettings() { const fileInput = document.getElementById('settings-file-input'); fileInput.click(); // 添加文件选择事件监听器 fileInput.onchange = function(e) { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { try { const importedSettings = JSON.parse(e.target.result); // 验证设置格式 if (typeof importedSettings === 'object' && importedSettings !== null) { // 保存导入的设置 saveSettings(importedSettings); // 更新设置界面 loadSettingsToUI(); // 显示提示 showInfobar('设置已成功导入'); // 重新初始化脚本逻辑 initLogic(); } else { showInfobar('导入失败:无效的设置格式'); } } catch { showInfobar('导入失败:JSON解析错误'); } }; reader.readAsText(file); } }; } // 创建设置界面 function createSettingsUI() { // 设置界面HTML结构 const settingsHTML = ` `; // 将设置界面添加到body document.body.insertAdjacentHTML('beforeend', settingsHTML); // 添加事件监听器 function addEventListeners() { // 关闭按钮点击事件 const closeBtn = document.getElementById('bilibili-auto-play-settings-close'); if (closeBtn) { closeBtn.addEventListener('click', hideSettings); } // 遮罩层点击事件 const overlay = document.getElementById('bilibili-auto-play-settings-overlay'); if (overlay) { overlay.addEventListener('click', function(e) { if (e.target === overlay) { hideSettings(); } }); } // 保存按钮点击事件 const saveBtn = document.getElementById('bilibili-auto-play-settings-save'); if (saveBtn) { saveBtn.addEventListener('click', saveSettingsFromUI); } // 导出设置按钮点击事件 const exportBtn = document.getElementById('export-settings-btn'); if (exportBtn) { exportBtn.addEventListener('click', exportSettings); } // 导入设置按钮点击事件 const importBtn = document.getElementById('import-settings-btn'); if (importBtn) { importBtn.addEventListener('click', importSettings); } // 选项卡切换事件 const tabItems = document.querySelectorAll('.tab-item'); tabItems.forEach(item => { item.addEventListener('click', function() { // 移除所有活动状态 tabItems.forEach(tab => tab.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); // 添加当前活动状态 const tabName = this.getAttribute('data-tab'); this.classList.add('active'); document.getElementById(`${tabName}-tab`).classList.add('active'); }); }); // 清空列表按钮事件 const clearBtn = document.getElementById('clear-collections-btn'); if (clearBtn) { clearBtn.addEventListener('click', function() { showConfirmDialog('确定要清空所有自动连播合集吗?', { confirmText: '确定', cancelText: '取消', onConfirm: clearCollectionList }); }); } } // 初始化事件监听器 addEventListeners(); // 监听系统主题变化 const themeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); themeMediaQuery.addEventListener('change', () => { applyTheme(); }); } // 显示提示信息 function showInfobar(message) { const settings = getSettings(); // 确保showInfobar属性存在,默认开启 if (settings.showInfobar === false) return; // 移除已存在的infobar const existingInfobar = document.getElementById('bilibili-auto-play-infobar'); if (existingInfobar) { existingInfobar.remove(); } // 创建infobar const infobarHTML = `
${message}
`; // 添加到body document.body.insertAdjacentHTML('beforeend', infobarHTML); // 3秒后自动移除 setTimeout(() => { const infobar = document.getElementById('bilibili-auto-play-infobar'); if (infobar) { infobar.remove(); } }, 3000); } // 显示自定义确认弹窗 function showConfirmDialog(message, options = {}) { // 移除已存在的弹窗 const existingDialog = document.getElementById('bilibili-auto-play-confirm-dialog'); if (existingDialog) { existingDialog.remove(); } // 创建弹窗HTML const dialogHTML = `
B站自动连播
${message}
${options.cancelText ? `` : ''} ${options.confirmText ? `` : ''}
`; // 添加到body document.body.insertAdjacentHTML('beforeend', dialogHTML); // 添加事件监听 const dialog = document.getElementById('bilibili-auto-play-confirm-dialog'); const cancelBtn = document.getElementById('bilibili-dialog-cancel'); const confirmBtn = document.getElementById('bilibili-dialog-confirm'); if (cancelBtn && options.onCancel) { cancelBtn.addEventListener('click', () => { options.onCancel(); dialog.remove(); }); } if (confirmBtn && options.onConfirm) { confirmBtn.addEventListener('click', () => { options.onConfirm(); dialog.remove(); }); } } // 显示右下角通知 function showNotification(message, options = {}) { // 移除已存在的通知 const existingNotification = document.getElementById('bilibili-auto-play-notification'); if (existingNotification) { existingNotification.remove(); } // 创建通知HTML const notificationHTML = `
B站自动连播
${message}
${options.cancelText ? `` : ''} ${options.confirmText ? `` : ''}
`; // 添加到body document.body.insertAdjacentHTML('beforeend', notificationHTML); // 添加事件监听 const notification = document.getElementById('bilibili-auto-play-notification'); const cancelBtn = document.getElementById('bilibili-notification-cancel'); const confirmBtn = document.getElementById('bilibili-notification-confirm'); if (cancelBtn && options.onCancel) { cancelBtn.addEventListener('click', () => { options.onCancel(); notification.remove(); }); } if (confirmBtn && options.onConfirm) { confirmBtn.addEventListener('click', () => { options.onConfirm(); notification.remove(); }); } // 自动关闭 - 只有当options.autoClose为true时才自动关闭,否则一直显示 if (options.autoClose === true) { setTimeout(() => { if (notification && notification.parentNode) { notification.remove(); if (options.onAutoClose) { options.onAutoClose(); } } }, options.autoCloseDelay || 8000); } } // 解析快捷键 function parseHotkey(hotkeyStr) { const keys = hotkeyStr.toLowerCase().split('+'); return { ctrl: keys.includes('ctrl'), shift: keys.includes('shift'), alt: keys.includes('alt'), meta: keys.includes('meta') || keys.includes('win') || keys.includes('cmd'), key: keys.find(key => !['ctrl', 'shift', 'alt', 'meta', 'win', 'cmd'].includes(key)) }; } // 检查是否匹配快捷键 function isHotkeyMatch(event, hotkeyConfig) { const hotkey = parseHotkey(hotkeyConfig); return ( event.ctrlKey === hotkey.ctrl && event.shiftKey === hotkey.shift && event.altKey === hotkey.alt && event.metaKey === hotkey.meta && event.key.toLowerCase() === hotkey.key ); } // 处理快捷键事件 function handleHotkey(event) { const settings = getSettings(); // 确保hotkeys属性存在 const hotkeys = settings.hotkeys || { toggleAutoPlay: 'Ctrl+Shift+L', toggleCollection: 'Ctrl+Shift+A' }; // 切换连播状态快捷键 if (isHotkeyMatch(event, hotkeys.toggleAutoPlay)) { event.preventDefault(); const switchBtn = document.querySelector('.continuous-btn .switch-btn'); if (switchBtn) { const isCurrentlyEnabled = switchBtn.classList.contains('on'); setAutoPlayState(!isCurrentlyEnabled); showInfobar(`连播已${!isCurrentlyEnabled ? '开启' : '关闭'}`); } } // 快捷指定当前合集快捷键 if (isHotkeyMatch(event, hotkeys.toggleCollection)) { event.preventDefault(); // 获取合集ID const collectionId = getCollectionId(); if (collectionId) { const settings = getSettings(); if (settings.enabledCollections.includes(collectionId)) { // 移除合集 removeCollection(collectionId); // 启用手动控制 enableManualControl(); // 根据设置决定连播状态 if (settings.autoEnableNonAdded !== false) { setAutoPlayState(true); } else { setAutoPlayState(false); } showInfobar('当前合集已从自动连播列表移除,连播按钮已解锁'); } else { // 添加合集 addCollection(collectionId); // 立即开启连播 setAutoPlayState(true); // 禁用手动控制 disableManualControl(); showInfobar('当前合集已添加到自动连播列表,连播按钮已锁定'); } } else { // 如果没有合集ID,使用视频ID作为备选 const videoId = getVideoIdFromUrl(); if (videoId) { const settings = getSettings(); if (settings.enabledCollections.includes(videoId)) { // 移除合集 removeCollection(videoId); // 启用手动控制 enableManualControl(); // 根据设置决定连播状态 if (settings.autoEnableNonAdded !== false) { setAutoPlayState(true); } else { setAutoPlayState(false); } showInfobar('当前合集已从自动连播列表移除,连播按钮已解锁'); } else { // 添加合集 addCollection(videoId); // 立即开启连播 setAutoPlayState(true); // 禁用手动控制 disableManualControl(); showInfobar('当前合集已添加到自动连播列表,连播按钮已锁定'); } } } } } // 注册快捷键监听 function registerHotkeyListener() { document.addEventListener('keydown', handleHotkey); } // 等待元素出现的工具函数 function waitForElement(selector, timeout = 5000) { return new Promise((resolve, reject) => { // 立即检查元素是否存在 const element = document.querySelector(selector); if (element) { resolve(element); return; } // 设置超时 const timer = setTimeout(() => { observer.disconnect(); reject(new Error(`Element not found: ${selector}`)); }, timeout); // 创建观察者 const observer = new MutationObserver(() => { const element = document.querySelector(selector); if (element) { clearTimeout(timer); observer.disconnect(); resolve(element); } }); // 开始观察 observer.observe(document.body, { childList: true, subtree: true }); }); } // 注入设置按钮到播放器控制栏 function injectSettingsButton() { // SVG图标内容 const settingsSvg = ` `; // 添加B站风格的Tooltip样式 const tooltipStyle = ` `; // 确保样式只添加一次 if (!document.querySelector('.bili-tooltip-style')) { const styleElement = document.createElement('div'); styleElement.className = 'bili-tooltip-style'; styleElement.innerHTML = tooltipStyle; document.head.appendChild(styleElement); } // 按钮HTML结构 const settingsButtonHtml = `
${settingsSvg}
`; // 注入按钮 const injectButton = async () => { // 检查按钮是否已经存在,使用更严格的检查 if (document.querySelector('.bpx-player-ctrl-btn.bpx-player-ctrl-settings')) { return; } try { // 等待bpx-player-control-bottom-right元素出现 const controlBottomRight = await waitForElement('.bpx-player-control-bottom-right'); // 再次检查按钮是否已经存在,防止在等待期间已经被注入 if (document.querySelector('.bpx-player-ctrl-btn.bpx-player-ctrl-settings')) { return; } // 直接在目标容器中创建按钮,避免DOM解析器导致的闪烁问题 // 创建按钮元素 const settingsBtn = document.createElement('div'); settingsBtn.className = 'bpx-player-ctrl-btn bpx-player-ctrl-settings'; settingsBtn.setAttribute('role', 'button'); settingsBtn.setAttribute('aria-label', '连播设置'); settingsBtn.style.cursor = 'pointer'; // 确保按钮可见 settingsBtn.style.display = 'flex'; settingsBtn.style.alignItems = 'center'; settingsBtn.style.justifyContent = 'center'; settingsBtn.style.color = 'white'; settingsBtn.style.fontSize = '16px'; settingsBtn.style.zIndex = '9999'; // 添加按钮图标 const btnIcon = document.createElement('div'); btnIcon.className = 'bpx-player-ctrl-btn-icon'; const svgContainer = document.createElement('span'); svgContainer.className = 'bpx-common-svg-icon'; svgContainer.innerHTML = settingsSvg; btnIcon.appendChild(svgContainer); settingsBtn.appendChild(btnIcon); // 直接插入到目标位置的最前面 controlBottomRight.insertBefore(settingsBtn, controlBottomRight.firstChild); // 添加调试信息 console.log('[B站连播控制] 按钮插入位置:', controlBottomRight); console.log('[B站连播控制] 按钮样式:', window.getComputedStyle(settingsBtn)); console.log('[B站连播控制] 按钮是否可见:', settingsBtn.offsetParent !== null); // 添加点击事件 settingsBtn.addEventListener('click', showSettings); // 创建并绑定Tooltip let tooltip = document.getElementById('bili-settings-tooltip'); if (!tooltip) { tooltip = document.createElement('div'); tooltip.id = 'bili-settings-tooltip'; tooltip.className = 'bili-tooltip'; tooltip.textContent = 'B站连播助手设置'; document.body.appendChild(tooltip); } // 绑定鼠标事件 let showTimeout; let hideTimeout; settingsBtn.addEventListener('mouseenter', () => { // 取消动画 settingsBtn.style.animation = 'none'; clearTimeout(hideTimeout); // 计算Tooltip位置 const rect = settingsBtn.getBoundingClientRect(); const tooltipRect = tooltip.getBoundingClientRect(); // 定位在按钮上方居中 const top = rect.top - tooltipRect.height - 8; const left = rect.left + (rect.width - tooltipRect.width) / 2; tooltip.style.top = `${top}px`; tooltip.style.left = `${left}px`; // 显示Tooltip showTimeout = setTimeout(() => { tooltip.classList.add('show'); }, 300); }); settingsBtn.addEventListener('mouseleave', () => { clearTimeout(showTimeout); // 隐藏Tooltip hideTimeout = setTimeout(() => { tooltip.classList.remove('show'); }, 100); }); console.log('[B站连播控制] 设置按钮注入成功'); } catch (error) { console.error('[B站连播控制] 设置按钮注入失败:', error); // 继续尝试,但增加延迟时间,避免频繁尝试 setTimeout(injectButton, 1000); } }; // 立即开始注入 injectButton(); // 使用MutationObserver监听DOM变化,确保按钮始终存在 // 但使用更严格的条件和防抖机制 let isInjecting = false; const observer = new MutationObserver(() => { // 检查按钮是否不存在,且当前没有正在注入的操作 if (!document.querySelector('.bpx-player-ctrl-btn.bpx-player-ctrl-settings') && !isInjecting) { isInjecting = true; // 延迟执行,避免频繁触发 setTimeout(() => { injectButton(); isInjecting = false; }, 500); } }); // 只监听播放器控制栏的变化,减少不必要的触发 const playerCtrl = document.querySelector('.bpx-player-ctrl'); if (playerCtrl) { observer.observe(playerCtrl, { childList: true, subtree: false }); } else { // 如果播放器控制栏不存在,回退到监听body,但添加更严格的条件 observer.observe(document.body, { childList: true, subtree: true }); } } // 在页面加载完成后注入设置按钮 function initButtonInjection() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { injectSettingsButton(); }); } else { injectSettingsButton(); } } // 观察更多播放设置区域,注入连播设置入口 function observeMoreSettingsArea() { // 创建一个MutationObserver来监听播放器控制栏的变化 const observer = new MutationObserver(() => { // 查找更多播放设置区域 const moreSettings = document.querySelector('.bpx-player-ctrl-setting-more'); if (moreSettings) { // 检查是否已经注入了连播设置入口 if (!document.getElementById('bilibili-auto-play-settings-entry')) { // 注入连播设置入口 const settingsEntry = document.createElement('div'); settingsEntry.id = 'bilibili-auto-play-settings-entry'; settingsEntry.className = 'bpx-player-ctrl-setting-auto-play'; settingsEntry.innerHTML = ` 连播设置 `; settingsEntry.style.cssText = ` display: flex; align-items: center; justify-content: space-between; padding: 10px 20px; cursor: pointer; transition: background-color 0.2s; `; // 添加点击事件,显示设置界面 settingsEntry.addEventListener('click', showSettings); // 添加悬停效果 settingsEntry.addEventListener('mouseenter', () => { settingsEntry.style.backgroundColor = 'rgba(255, 255, 255, 0.1)'; }); settingsEntry.addEventListener('mouseleave', () => { settingsEntry.style.backgroundColor = 'transparent'; }); // 将设置入口添加到更多播放设置下方 moreSettings.parentNode.appendChild(settingsEntry); } // 查找更多播放设置的右侧面板 const moreSettingsRight = document.querySelector('.bpx-player-ctrl-setting-menu-right'); if (moreSettingsRight) { // 检查是否已经注入了连播设置面板 if (!document.getElementById('bilibili-auto-play-settings-panel')) { // 注入连播设置面板 const settingsPanel = document.createElement('div'); settingsPanel.id = 'bilibili-auto-play-settings-panel'; settingsPanel.className = 'bpx-player-ctrl-setting-autoplay-control'; settingsPanel.innerHTML = `
连播控制
快捷操作
`; // 设置面板样式 settingsPanel.style.cssText = ` margin-top: 16px; padding-top: 16px; border-top: 1px solid rgba(255, 255, 255, 0.1); `; // 添加到更多播放设置右侧面板 moreSettingsRight.appendChild(settingsPanel); // 绑定设置面板的事件 bindSettingsPanelEvents(); } } } }); // 开始观察播放器容器 const playerContainer = document.querySelector('.bpx-player'); if (playerContainer) { observer.observe(playerContainer, { childList: true, subtree: true }); } else { // 如果播放器容器不存在,回退到监听body observer.observe(document.body, { childList: true, subtree: true }); } } // 绑定设置面板的事件 function bindSettingsPanelEvents() { // 启用自动连播控制 const enableCheckbox = document.getElementById('autoplay-control-enable'); if (enableCheckbox) { enableCheckbox.checked = true; // 默认启用 enableCheckbox.addEventListener('change', () => { // 这里可以添加启用/禁用连播控制的逻辑 showInfobar(`连播控制已${enableCheckbox.checked ? '启用' : '禁用'}`); }); } // 显示提示信息 const infobarCheckbox = document.getElementById('autoplay-control-infobar'); if (infobarCheckbox) { // 加载当前设置 const settings = getSettings(); infobarCheckbox.checked = settings.showInfobar !== false; infobarCheckbox.addEventListener('change', () => { const settings = getSettings(); settings.showInfobar = infobarCheckbox.checked; saveSettings(settings); showInfobar(`提示信息已${infobarCheckbox.checked ? '启用' : '禁用'}`); }); } // 不添加时自动开启连播 const autoEnableCheckbox = document.getElementById('autoplay-control-auto-enable'); if (autoEnableCheckbox) { // 加载当前设置 const settings = getSettings(); autoEnableCheckbox.checked = settings.autoEnableNonAdded !== false; autoEnableCheckbox.addEventListener('change', () => { const settings = getSettings(); settings.autoEnableNonAdded = autoEnableCheckbox.checked; saveSettings(settings); showInfobar(`不添加时自动开启连播已${autoEnableCheckbox.checked ? '启用' : '禁用'}`); }); } // 添加当前合集 const addBtn = document.getElementById('autoplay-control-add'); if (addBtn) { addBtn.addEventListener('click', () => { const videoId = getVideoIdFromUrl(); if (videoId) { addCollection(videoId); // 立即开启连播 setAutoPlayState(true); // 禁用手动控制 disableManualControl(); } }); } // 移除当前合集 const removeBtn = document.getElementById('autoplay-control-remove'); if (removeBtn) { removeBtn.addEventListener('click', () => { const videoId = getVideoIdFromUrl(); if (videoId) { removeCollection(videoId); } }); } // 查看设置 const viewBtn = document.getElementById('autoplay-control-view'); if (viewBtn) { viewBtn.addEventListener('click', showSettings); } } // 启动脚本 init(); })();