使用 bsbsb.top API 跳过标注片段,并以绿色在进度条上标注广告时段
目前為
// ==UserScript==
// @license MIT
// @name BilibiliSponsorBlock-Tampermonkey
// @namespace https://github.com/MCfengyou/BilibiliSponsorBlock-Tampermonkey
// @version 1.0
// @description 使用 bsbsb.top API 跳过标注片段,并以绿色在进度条上标注广告时段
// @author NeoGe_and_GPT-5
// @match https://www.bilibili.com/video/*
// @match https://www.bilibili.com/bangumi/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
const API = 'https://bsbsb.top/api/skipSegments?videoID=';
const LOG = (...a)=>console.log('[BSB+ FIX6R4]',...a);
let currentBV=null, segments=[], video=null, observer=null, markerLayer=null, pendingPrompt=null, promptTimer=null;
let renderScheduled=false;
let manualInSegment=false; // ✅ 新增:标记用户主动跳入广告区
let userSeeking=false;
let lastTime=0;
const CATEGORY_RULES={
intro:{label:'过场/开场动画',color:'rgb(0,255,255)',mode:'manual'},
selfpromo:{label:'无偿/自我推广',color:'rgb(255,255,0)',mode:'manual'},
sponsor:{label:'赞助/恰饭',color:'rgb(0,212,0)',mode:'auto'},
interaction:{label:'三连/互动提醒',color:'rgb(204,0,255)',mode:'manual'},
preview:{label:'回顾/概要',color:'rgb(0,143,214)',mode:'marker'},
outro:{label:'鸣谢/结束画面',color:'rgb(2,2,237)',mode:'manual'}
};
function getBV(){const m=location.href.match(/BV[\w]+/);return m?m[0]:null;}
async function getSegments(bv){
try{const r=await fetch(API+bv);const j=await r.json();
return (Array.isArray(j)?j:[]).map(s=>({start:s.segment[0],end:s.segment[1],category:s.category}))
.filter(s=>CATEGORY_RULES[s.category]);
}catch(e){LOG('API error',e);return [];}
}
function findVideo(){return document.querySelector('video');}
function findProgress(){
return document.querySelector('.bpx-player-progress-wrap')
|| document.querySelector('.bilibili-player-video-progress')
|| document.querySelector('.bpx-player-container .bpx-player-progress');
}
function renderMarkers(){
const progress=findProgress();
if(!video||!progress)return;
if(!markerLayer){
markerLayer=document.createElement('div');
Object.assign(markerLayer.style,{position:'absolute',left:0,top:0,width:'100%',height:'100%',pointerEvents:'none'});
progress.style.position='relative';
progress.appendChild(markerLayer);
}
const dur=video.duration||0;if(!dur)return;
const html=segments.map(s=>{
const r=CATEGORY_RULES[s.category];if(!r)return'';
const l=(s.start/dur*100),w=((s.end-s.start)/dur*100);
return `<div style="position:absolute;left:${l}%;width:${w}%;height:100%;background:${r.color};opacity:.45;border-radius:2px;"></div>`;
}).join('');
markerLayer.innerHTML=html;
LOG('Markers rendered',segments.length);
}
function delayedMarkerRender(retries=10){
const tryRender=()=>{
const p=findProgress();
if(p){renderMarkers();return;}
if(--retries>0)setTimeout(tryRender,800);
};
tryRender();
}
function showNotice(msg,color='rgba(0,212,0,.9)'){
const d=document.createElement('div');
Object.assign(d.style,{position:'fixed',right:'40px',bottom:'120px',background:color,color:'#fff',
padding:'8px 12px',borderRadius:'8px',fontSize:'14px',zIndex:999999,opacity:'0',transition:'opacity .3s'});
d.textContent=msg;document.body.appendChild(d);
requestAnimationFrame(()=>d.style.opacity='1');
setTimeout(()=>d.style.opacity='0',1600);
setTimeout(()=>d.remove(),2000);
}
function removePrompt(){const el=document.getElementById('bsb-prompt');if(el)el.remove();if(promptTimer)clearInterval(promptTimer);pendingPrompt=null;}
function showManualPrompt(seg,rule){
removePrompt();
const div=document.createElement('div');
div.id='bsb-prompt';
div.style.cssText='position:fixed;right:40px;bottom:120px;background:rgba(0,0,0,.7);color:#fff;padding:10px 14px;border-radius:10px;font-size:14px;z-index:999999;display:flex;align-items:center;gap:8px;';
div.innerHTML=`<span style="color:${rule.color}">[${rule.label}] | 按 Enter 键跳过 (<span id="bsb-count">5</span>s)</span>
<span id="bsb-close" style="cursor:pointer;margin-left:6px;">✕</span>`;
document.body.appendChild(div);
pendingPrompt={seg,rule};let sec=5;
promptTimer=setInterval(()=>{sec--;const c=document.getElementById('bsb-count');if(c)c.textContent=sec;if(sec<=0)removePrompt();},1000);
document.getElementById('bsb-close').onclick=removePrompt;
}
window.addEventListener('keydown',e=>{
if(e.key==='Enter'&&pendingPrompt){
video.currentTime=Math.min(pendingPrompt.seg.end+0.05,video.duration);
showNotice(`${pendingPrompt.rule.label} 已跳过`,pendingPrompt.rule.color);
removePrompt();
}
});
function inAdSegment(t){
return segments.find(s=>t>=s.start && t<s.end);
}
function handleTime(){
if(!video)return;
const t=video.currentTime;
const seg=inAdSegment(t);
if(!seg){
manualInSegment=false; // 用户已离开广告段
return;
}
const rule=CATEGORY_RULES[seg.category];
if(!rule)return;
// 如果用户是手动seek进入广告段,则允许观看(不跳)
if(manualInSegment || userSeeking){
return;
}
// 自动跳过或手动提示(自然播放进入)
if(rule.mode==='auto'){
video.currentTime=Math.min(seg.end+0.05,video.duration);
showNotice(`${rule.label} 已跳过`,rule.color);
}else if(rule.mode==='manual'&&!pendingPrompt){
showManualPrompt(seg,rule);
}
}
function attachSeekListeners(){
video.addEventListener('seeking',()=>{
userSeeking=true;
const t=video.currentTime;
if(inAdSegment(t)){manualInSegment=true;LOG('User manually entered ad segment');}
});
video.addEventListener('seeked',()=>{
setTimeout(()=>{userSeeking=false;},800);
});
}
function safeRender(){if(renderScheduled)return;renderScheduled=true;setTimeout(()=>{renderMarkers();renderScheduled=false;},500);}
function observeProgress(){
if(observer)observer.disconnect();
const target=findProgress();
if(!target)return;
observer=new MutationObserver(safeRender);
observer.observe(target,{childList:true,subtree:true});
LOG('Observer attached to progress bar');
}
async function init(){
const bv=getBV();if(!bv||bv===currentBV)return;
currentBV=bv;LOG('Init for',bv);
video=findVideo();if(!video){setTimeout(init,800);return;}
attachSeekListeners();
segments=await getSegments(bv);
renderMarkers();
delayedMarkerRender();
observeProgress();
video.ontimeupdate=handleTime;
video.onloadedmetadata=()=>delayedMarkerRender();
}
let last=location.href;
setInterval(()=>{if(location.href!==last){last=location.href;setTimeout(init,1000);}},1000);
init();
})();