Skip to content

Instantly share code, notes, and snippets.

@xebecnan
Last active July 18, 2026 04:04
Show Gist options
  • Select an option

  • Save xebecnan/466f2d8ca0b25367d64ac43d3268772a to your computer and use it in GitHub Desktop.

Select an option

Save xebecnan/466f2d8ca0b25367d64ac43d3268772a to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name B站列表随机播放
// @namespace http://tampermonkey.net/
// @version 1.0.2
// @description 在B站视频页右下角提供一个可展开的随机播放按钮,视频结束后从当前页面可识别列表中随机跳转下一项
// @author ChatGPT
// @match https://www.bilibili.com/video/*
// @match https://www.bilibili.com/list/*
// @grant none
// @run-at document-idle
// @license MIT
// @downloadURL https://update.greasyfork.org/scripts/573854/B%E7%AB%99%E5%88%97%E8%A1%A8%E9%9A%8F%E6%9C%BA%E6%92%AD%E6%94%BE.user.js
// @updateURL https://update.greasyfork.org/scripts/573854/B%E7%AB%99%E5%88%97%E8%A1%A8%E9%9A%8F%E6%9C%BA%E6%92%AD%E6%94%BE.meta.js
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY_ENABLED = '__bili_random_playlist_enabled__';
const STORAGE_KEY_HISTORY = '__bili_random_playlist_history__';
const STORAGE_KEY_PANEL_OPEN = '__bili_random_playlist_panel_open__';
const STORAGE_KEY_WEB_FULLSCREEN = '__bili_random_playlist_web_fullscreen__';
const MAX_HISTORY = 50;
const ROOT_ID = 'bili-random-play-root';
const STYLE_ID = 'bili-random-play-style';
function log(...args) {
console.log('[BiliRandomSimple]', ...args);
}
function getEnabled() {
return localStorage.getItem(STORAGE_KEY_ENABLED) === '1';
}
function setEnabled(val) {
localStorage.setItem(STORAGE_KEY_ENABLED, val ? '1' : '0');
}
function getPanelOpen() {
return localStorage.getItem(STORAGE_KEY_PANEL_OPEN) === '1';
}
function setPanelOpen(val) {
localStorage.setItem(STORAGE_KEY_PANEL_OPEN, val ? '1' : '0');
}
function getWebFullscreenEnabled() {
return localStorage.getItem(STORAGE_KEY_WEB_FULLSCREEN) === '1';
}
function setWebFullscreenEnabled(val) {
localStorage.setItem(STORAGE_KEY_WEB_FULLSCREEN, val ? '1' : '0');
}
function getHistory() {
try {
return JSON.parse(localStorage.getItem(STORAGE_KEY_HISTORY) || '[]');
} catch {
return [];
}
}
function setHistory(arr) {
localStorage.setItem(STORAGE_KEY_HISTORY, JSON.stringify(arr.slice(-MAX_HISTORY)));
}
function getCurrentVideoId() {
const match = location.pathname.match(/\/video\/(BV[\w]+)/i);
return match ? match[1] : null;
}
function normalizeUrl(url) {
try {
const u = new URL(url, location.origin);
return u.origin + u.pathname + u.search;
} catch {
return url;
}
}
function extractBvid(url) {
const match = String(url).match(/\/video\/(BV[\w]+)/i);
return match ? match[1] : null;
}
function getPlaylistAnchors() {
const selectors = [
'a[href*="/video/BV"]',
'.video-pod__list a[href*="/video/BV"]',
'.base-video-sections-v1 a[href*="/video/BV"]',
'.next-play-list a[href*="/video/BV"]',
'.video-section-list a[href*="/video/BV"]',
'.pod-item a[href*="/video/BV"]',
'.list-box a[href*="/video/BV"]'
];
const found = new Map();
for (const selector of selectors) {
document.querySelectorAll(selector).forEach((a) => {
const href = a.href || a.getAttribute('href');
if (!href || !href.includes('/video/')) return;
const fullUrl = normalizeUrl(href);
const bvid = extractBvid(fullUrl);
if (!bvid) return;
if (!found.has(fullUrl)) {
found.set(fullUrl, {
url: fullUrl,
bvid,
title: (a.textContent || '').trim()
});
}
});
}
return Array.from(found.values());
}
function pickRandomNext(items) {
if (!items.length) return null;
const currentBvid = getCurrentVideoId();
const history = getHistory();
let candidates = items.filter(item => item.bvid !== currentBvid);
if (!candidates.length) return null;
let fresh = candidates.filter(item => !history.includes(item.bvid));
if (!fresh.length) {
setHistory(currentBvid ? [currentBvid] : []);
fresh = candidates;
}
const randomIndex = Math.floor(Math.random() * fresh.length);
return fresh[randomIndex];
}
function jumpToVideo(item) {
if (!item || !item.url) return;
log('跳转到随机视频:', item);
location.href = item.url;
}
function triggerWebFullscreen() {
// 如果用户没开启此功能,直接返回
if (!getWebFullscreenEnabled()) return;
// 查找 B站播放器的“网页全屏”按钮
// B站新版播放器通常使用 .bpx-player-ctrl-web-btn 类名
// 也可以通过 title="网页全屏" 或 aria-label 来查找作为后备
const webFsBtn = document.querySelector('.bpx-player-ctrl-web-btn')
|| document.querySelector('[title="网页全屏"]');
if (webFsBtn) {
// 检查当前是否已经处于网页全屏状态
// B站播放器容器通常会有 .bpx-state-web-fullscreen 类
const playerContainer = document.querySelector('.bpx-player-container') || document.querySelector('#bilibili-player');
const isNowWebFs = playerContainer && playerContainer.classList.contains('bpx-state-web-fullscreen');
// 如果当前不是网页全屏,则点击
if (!isNowWebFs) {
log('检测到开启指令,正在进入网页全屏...');
webFsBtn.click();
} else {
log('当前已是网页全屏模式');
}
} else {
log('未找到网页全屏按钮,可能播放器未加载或结构变更');
}
}
function markCurrentPlayed() {
const currentBvid = getCurrentVideoId();
if (!currentBvid) return;
const history = getHistory();
if (!history.includes(currentBvid)) {
history.push(currentBvid);
setHistory(history);
}
}
function updateToggleButtonUI() {
const toggleBtn = document.getElementById('bili-random-toggle-btn');
if (!toggleBtn) return;
const enabled = getEnabled();
toggleBtn.textContent = enabled ? '随机播放:开' : '随机播放:关';
toggleBtn.style.background = enabled ? '#00aeec' : '#666';
}
function updatePanelUI() {
const root = document.getElementById(ROOT_ID);
const miniBtn = document.getElementById('bili-random-mini-btn');
const panel = document.getElementById('bili-random-panel');
if (!root || !miniBtn || !panel) return;
const open = getPanelOpen();
panel.style.display = open ? 'flex' : 'none';
miniBtn.textContent = open ? '收起' : '随机';
updateToggleButtonUI();
}
function handleVideoEnded() {
if (!getEnabled()) return;
markCurrentPlayed();
const items = getPlaylistAnchors();
log('当前识别到播放列表项目数:', items.length);
if (items.length <= 1) {
log('未识别到有效播放列表,无法随机切换');
return;
}
const next = pickRandomNext(items);
if (!next) {
log('没有可跳转的随机视频');
return;
}
setTimeout(() => {
jumpToVideo(next);
}, 800);
}
function bindVideoEnded(video) {
if (!video || video.dataset.randomBound === '1') return;
video.dataset.randomBound = '1';
video.addEventListener('ended', handleVideoEnded);
log('已绑定 ended 事件');
}
function observeVideo() {
let lastVideo = null;
const timer = setInterval(() => {
const video = document.querySelector('video');
if (video && video !== lastVideo) {
lastVideo = video;
bindVideoEnded(video);
// 【新增】当检测到 video 标签变化(意味着新视频加载),尝试触发全屏
triggerWebFullscreen();
}
}, 1500);
window.addEventListener('beforeunload', () => {
clearInterval(timer);
});
}
function injectStyle() {
if (document.getElementById(STYLE_ID)) return;
const style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = `
#${ROOT_ID} {
position: fixed;
right: 20px;
bottom: 20px;
z-index: 999999;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 10px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
#${ROOT_ID} #bili-random-mini-btn {
width: 56px;
height: 56px;
border: none;
border-radius: 50%;
background: #00aeec;
color: #fff;
font-size: 14px;
cursor: pointer;
box-shadow: 0 4px 16px rgba(0,0,0,.2);
}
#${ROOT_ID} #bili-random-mini-btn:hover {
opacity: 0.95;
}
#${ROOT_ID} #bili-random-panel {
display: none;
flex-direction: column;
gap: 8px;
min-width: 150px;
padding: 12px;
border-radius: 12px;
background: rgba(255,255,255,0.96);
box-shadow: 0 4px 20px rgba(0,0,0,.18);
border: 1px solid rgba(0,0,0,.08);
backdrop-filter: blur(6px);
}
#${ROOT_ID} .bili-random-btn {
border: none;
border-radius: 8px;
padding: 10px 12px;
font-size: 14px;
cursor: pointer;
color: #fff;
}
#${ROOT_ID} #bili-random-toggle-btn {
background: #666;
}
#${ROOT_ID} #bili-random-reset-btn {
background: #999;
}
#${ROOT_ID} .bili-random-btn:hover {
opacity: 0.94;
}
#${ROOT_ID} #bili-random-web-fullscreen-btn {
background: #FF9500; /* 用橙色区分,表示这是一个辅助功能 */
}
`;
document.head.appendChild(style);
}
function createFloatingPanel() {
if (document.getElementById(ROOT_ID)) return;
injectStyle();
const root = document.createElement('div');
root.id = ROOT_ID;
const panel = document.createElement('div');
panel.id = 'bili-random-panel';
const toggleBtn = document.createElement('button');
toggleBtn.id = 'bili-random-toggle-btn';
toggleBtn.className = 'bili-random-btn';
toggleBtn.textContent = getEnabled() ? '随机播放:开' : '随机播放:关';
toggleBtn.addEventListener('click', () => {
const nextState = !getEnabled();
setEnabled(nextState);
if (nextState) {
markCurrentPlayed();
}
updateToggleButtonUI();
log('随机播放状态:', nextState ? '开启' : '关闭');
});
// 新增:自动网页全屏开关
const webFsBtn = document.createElement('button');
webFsBtn.id = 'bili-random-web-fullscreen-btn';
webFsBtn.className = 'bili-random-btn';
webFsBtn.textContent = getWebFullscreenEnabled() ? '自动网页全屏:开' : '自动网页全屏:关';
webFsBtn.addEventListener('click', () => {
const nextState = !getWebFullscreenEnabled();
setWebFullscreenEnabled(nextState);
webFsBtn.textContent = nextState ? '自动网页全屏:开' : '自动网页全屏:关';
webFsBtn.style.background = nextState ? '#FF9500' : '#999';
// 如果开启了,立即尝试执行一次(方便用户即时看到效果)
if (nextState) {
triggerWebFullscreen();
}
log('自动网页全屏状态:', nextState ? '开启' : '关闭');
});
const resetBtn = document.createElement('button');
resetBtn.id = 'bili-random-reset-btn';
resetBtn.className = 'bili-random-btn';
resetBtn.textContent = '重置历史';
resetBtn.addEventListener('click', () => {
const currentBvid = getCurrentVideoId();
setHistory(currentBvid ? [currentBvid] : []);
log('播放历史已重置');
});
panel.appendChild(toggleBtn);
panel.appendChild(webFsBtn); // 新增
panel.appendChild(resetBtn);
const miniBtn = document.createElement('button');
miniBtn.id = 'bili-random-mini-btn';
miniBtn.textContent = getPanelOpen() ? '收起' : '随机';
miniBtn.addEventListener('click', () => {
const nextOpen = !getPanelOpen();
setPanelOpen(nextOpen);
updatePanelUI();
});
root.appendChild(panel);
root.appendChild(miniBtn);
document.body.appendChild(root);
updatePanelUI();
}
function init() {
createFloatingPanel();
observeVideo();
log('脚本已启动');
}
window.addEventListener('load', () => {
setTimeout(init, 1200);
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment