// ==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 = `
自动连播合集列表
以下是当前已添加到自动连播列表的合集ID,点击删除按钮可移除特定合集
`;
// 将设置界面添加到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 = `
`;
// 注入按钮
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();
})();