// ==UserScript==
// @name B站PCDN拦截器
// @namespace lllbbbzzz
// @version 8.1
// @description 拦截B站PCDN视频源,主动替换为正规CDN。防止成为PCDN节点
// @author lllbbbzzz
// @icon https://bsyimg.luoca.net/imgtc/20260321/d627bb756497649e6aa216c64a0ff050.webp
// @match *://*.bilibili.com/*
// @match *://*.bilibili.tv/*
// @match *://*.bilivideo.cn/*
// @run-at document-start
// @grant GM_registerMenuCommand
// @license MIT
// ==/UserScript==
(function () {
'use strict';
var KEY = 'pcdn_killer_v8';
var IND = 'pcdn-ind';
var enabled = localStorage.getItem(KEY) !== '0';
var count = 0;
/* ════════ 页面判断 ════════ */
function isVideoPage() {
var p = location.pathname;
return /^\/video\//.test(p)
|| /^\/bangumi\/play\//.test(p)
|| /^\/medialist\/play\//.test(p)
|| /^\/cheese\/play\//.test(p);
}
/* ════════ PCDN 检测 ════════ */
function isPCDN(u) {
return u && (/\.mcdn\.bilivideo\.cn/i.test(u) || /xy\d+x\d+x\d+x\d+xy/i.test(u));
}
function isSegment(u) {
return u && (/\/seg\//i.test(u) || /\d+-\d+\.(m4s|ts)(\?|$)/i.test(u));
}
function isPCDNSignaling(u) {
if (!u) return false;
return /p2p|pcdn|mcdn|webrtc|signal|stun|turn/i.test(u);
}
/* ════════ 防止被当作PCDN节点(核心新增)════════ */
// 1. 禁用 WebRTC(P2P传输的核心)
function blockWebRTC() {
if (!window.RTCPeerConnection) return;
var OrigRTC = window.RTCPeerConnection;
window.RTCPeerConnection = function (config, constraints) {
// 检查是否为B站PCDN信令
if (config) {
var servers = [];
if (config.iceServers) {
config.iceServers.forEach(function (s) {
if (s.urls) {
var urls = Array.isArray(s.urls) ? s.urls : [s.urls];
servers = servers.concat(urls);
}
});
}
var isPCDNRTC = servers.some(function (u) {
return isPCDN(u) || isPCDNSignaling(u);
});
if (isPCDNRTC) {
count++;
render();
console.log('%c[PCDN Killer] 阻止WebRTC P2P连接', 'color:#ff9800;font-weight:bold');
// 返回一个无用的空连接,不报错不崩溃
return new OrigRTC({
iceServers: [{ urls: 'stun:0.0.0.0' }]
});
}
}
return new OrigRTC(config, constraints);
};
window.RTCPeerConnection.prototype = OrigRTC.prototype;
// 保留静态方法
['generateCertificate', 'getRTCStats'].forEach(function (m) {
if (OrigRTC[m]) window.RTCPeerConnection[m] = OrigRTC[m];
});
}
// 2. 阻止 PCDN 相关 WebSocket
function blockPCDNWebSocket() {
var OrigWS = window.WebSocket;
window.WebSocket = function (url, protocols) {
if (enabled && isPCDNSignaling(url)) {
count++;
render();
console.log('%c[PCDN Killer] 阻止PCDN WebSocket: ' + url.slice(0, 80), 'color:#ff9800;font-weight:bold');
// 返回一个立即关闭的假WebSocket
var ws = protocols !== undefined ? new OrigWS(url, protocols) : new OrigWS(url);
setTimeout(function () { try { ws.close(); } catch (_) { /* ignore */ } }, 0);
return ws;
}
return protocols !== undefined ? new OrigWS(url, protocols) : new OrigWS(url);
};
window.WebSocket.prototype = OrigWS.prototype;
}
// 3. 阻止注册 Service Worker(部分PCDN通过SW代理请求)
function blockPCDNServiceWorker() {
if (!navigator.serviceWorker) return;
var origReg = navigator.serviceWorker.register;
navigator.serviceWorker.register = function (scriptURL, options) {
var url = typeof scriptURL === 'string' ? scriptURL : '';
if (/p2p|pcdn|mcdn|cdn|sw-proxy/i.test(url)) {
count++;
render();
console.log('%c[PCDN Killer] 阻止注册PCDN ServiceWorker', 'color:#ff9800;font-weight:bold');
return Promise.reject(new DOMException('blocked', 'AbortError'));
}
return origReg.apply(this, arguments);
};
}
// 4. 阻止 SharedWorker(部分PCDN方案使用)
function blockPCDNSharedWorker() {
if (!window.SharedWorker) return;
var OrigSW = window.SharedWorker;
window.SharedWorker = function (scriptURL, options) {
var url = typeof scriptURL === 'string' ? scriptURL : '';
if (/p2p|pcdn|mcdn/i.test(url)) {
count++;
render();
console.log('%c[PCDN Killer] 阻止PCDN SharedWorker', 'color:#ff9800;font-weight:bold');
return { port: { start: function () { /* noop */ } } };
}
return new OrigSW(scriptURL, options);
};
window.SharedWorker.prototype = OrigSW.prototype;
}
// 5. 阻止创建 Web Worker(加载PCDN脚本)
function blockPCDNWorker() {
var OrigWorker = window.Worker;
window.Worker = function (scriptURL, options) {
var url = typeof scriptURL === 'string' ? scriptURL : '';
if (/p2p|pcdn|mcdn/i.test(url)) {
count++;
render();
console.log('%c[PCDN Killer] 阻止PCDN Worker', 'color:#ff9800;font-weight:bold');
return {
postMessage: function () { /* noop */ },
terminate: function () { /* noop */ },
addEventListener: function () { /* noop */ }
};
}
return new OrigWorker(scriptURL, options);
};
window.Worker.prototype = OrigWorker.prototype;
}
// 6. 拦截 navigator.sendBeacon(上报PCDN统计)
function blockPCDNBeacon() {
if (!navigator.sendBeacon) return;
var origBeacon = navigator.sendBeacon;
navigator.sendBeacon = function (url, data) {
if (enabled && isPCDNSignaling(url)) {
console.log('%c[PCDN Killer] 阻止PCDN上报', 'color:#ff9800', url.slice(0, 80));
return true;
}
return origBeacon.apply(this, arguments);
};
}
/* ════════ 主动替换CDN ════════ */
function replaceCDN(json) {
if (!enabled || !json || !json.dash) return json;
var legitPaths = [];
['video', 'audio'].forEach(function (t) {
if (!json.dash[t]) return;
json.dash[t].forEach(function (s) {
if (s.base_url && !isPCDN(s.base_url)) {
legitPaths.push(s.base_url);
}
if (s.backup_url) {
s.backup_url.forEach(function (u) {
if (!isPCDN(u)) legitPaths.push(u);
});
}
});
});
if (legitPaths.length === 0) return json;
var legitHosts = [];
legitPaths.forEach(function (u) {
try {
var h = new URL(u, location.origin).host;
if (legitHosts.indexOf(h) === -1) legitHosts.push(h);
} catch (_) { /* ignore */ }
});
if (legitHosts.length === 0) return json;
var dirty = false;
['video', 'audio'].forEach(function (t) {
if (!json.dash[t]) return;
json.dash[t].forEach(function (s) {
var newBackups = [];
if (s.backup_url) {
s.backup_url.forEach(function (u) {
if (!isPCDN(u)) {
newBackups.push(u);
} else {
dirty = true;
}
});
}
if (isPCDN(s.base_url)) {
if (newBackups.length > 0) {
s.base_url = newBackups[0];
} else {
var path = s.base_url.replace(/^https?:\/\/[^/]+/, '');
s.base_url = 'https://' + legitHosts[0] + path;
}
dirty = true;
}
while (newBackups.length < 2 && legitHosts.length > 0) {
var host = legitHosts[newBackups.length % legitHosts.length];
var ref = s.base_url.replace(/^https?:\/\/[^/]+/, '');
newBackups.push('https://' + host + ref);
}
s.backup_url = newBackups;
});
});
if (dirty) {
count++;
render();
console.log('%c[PCDN Killer] CDN源已替换为正规线路', 'color:#00c853;font-weight:bold');
}
return json;
}
/* ════════ Hook fetch ════════ */
var _fetch = window.fetch;
window.fetch = function (input) {
var url = typeof input === 'string' ? input : (input && input.url) || '';
var isPlay = /\/playurl/i.test(url);
// 拦截PCDN分片
if (enabled && isPCDN(url) && isSegment(url)) {
count++;
render();
console.log('%c[PCDN Killer] 拦截分片', 'color:#ff9800', url.slice(0, 100));
return Promise.reject(new DOMException('blocked', 'AbortError'));
}
// 拦截PCDN信令请求
if (enabled && isPCDNSignaling(url) && !isLegitimateCDN(url)) {
console.log('%c[PCDN Killer] 拦截信令', 'color:#ff9800', url.slice(0, 100));
return Promise.reject(new DOMException('blocked', 'AbortError'));
}
return _fetch.apply(this, arguments).then(function (res) {
if (!enabled || !isPlay) return res;
return res.clone().text().then(function (text) {
try {
var filtered = replaceCDN(JSON.parse(text));
return new Response(JSON.stringify(filtered), {
status: res.status,
statusText: res.statusText,
headers: res.headers
});
} catch (_) { return res; }
});
});
};
/* ════════ Hook XMLHttpRequest ════════ */
var _open = XMLHttpRequest.prototype.open;
var _send = XMLHttpRequest.prototype.send;
var _rtDesc = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, 'responseText');
XMLHttpRequest.prototype.open = function (m, u) {
this._u = u;
this._isPlay = /\/playurl/i.test(u);
return _open.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function () {
var self = this;
if (enabled && isPCDN(this._u) && isSegment(this._u)) {
count++;
render();
console.log('%c[PCDN Killer] XHR拦截', 'color:#ff9800', this._u.slice(0, 100));
try { this.abort(); } catch (_) { /* ignore */ }
return;
}
if (enabled && this._isPlay) {
this.addEventListener('readystatechange', function () {
if (self.readyState !== 4 || self.status !== 200) return;
try {
var text = _rtDesc.get.call(self);
var filtered = replaceCDN(JSON.parse(text));
var out = JSON.stringify(filtered);
Object.defineProperty(self, 'responseText', { value: out, writable: true, configurable: true });
Object.defineProperty(self, 'response', { value: out, writable: true, configurable: true });
} catch (_) { /* ignore */ }
});
}
return _send.apply(this, arguments);
};
/* ════════ 指示器 ════════ */
function pos() {
var sels = ['#viewbox_report h1', '.video-title-href', 'h1.video-title', '.video-title'];
for (var i = 0; i < sels.length; i++) {
var el = document.querySelector(sels[i]);
if (!el) continue;
var r = el.getBoundingClientRect();
if (r.width > 0 && r.height > 0) {
return { top: r.top + scrollY + (r.height - 28) / 2, left: r.right + 12 };
}
}
return null;
}
function build() {
var el = document.getElementById(IND) || document.createElement('div');
el.id = IND;
var p = pos() || { top: 70, left: 10 };
var bg = enabled ? 'linear-gradient(135deg,#00c853,#00a843)' : 'linear-gradient(135deg,#e53935,#c62828)';
var label = enabled ? 'PCDN拦截' : 'PCDN·关';
if (enabled && count > 0) label += ' · ' + count;
el.style.cssText = 'position:absolute;top:' + p.top + 'px;left:' + p.left + 'px;display:inline-flex;align-items:center;gap:5px;padding:3px 12px 3px 9px;border-radius:14px;font-size:12px;font-weight:600;color:#fff;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;cursor:pointer;user-select:none;line-height:1.6;background:' + bg + ';box-shadow:0 1px 6px rgba(0,0,0,.25);z-index:99999;transition:background .3s';
el.innerHTML = '' + label + '';
el.title = 'PCDN拦截: ' + (enabled ? '开启' : '关闭') + (enabled && count ? '\n本次拦截 ' + count + ' 次' : '') + '\n点击切换';
el.onclick = toggle;
return el;
}
function render() {
var old = document.getElementById(IND);
if (old) old.replaceWith(build());
}
function ensure() {
if (!document.body) return;
if (!isVideoPage()) {
var old = document.getElementById(IND);
if (old) old.remove();
return;
}
document.getElementById(IND) ? render() : document.body.appendChild(build());
}
/* ════════ 开关 ════════ */
function toggle() {
enabled = !enabled;
if (!enabled) count = 0;
localStorage.setItem(KEY, enabled ? '1' : '0');
render();
}
try { GM_registerMenuCommand('PCDN拦截切换', toggle); } catch (_) { /* ignore */ }
/* ════════ 初始化所有防护 ════════ */
blockWebRTC();
blockPCDNWebSocket();
blockPCDNServiceWorker();
blockPCDNSharedWorker();
blockPCDNWorker();
blockPCDNBeacon();
/* ════════ 定时守护 ════════ */
setInterval(function () { if (document.body) ensure(); }, 1000);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', ensure);
} else {
ensure();
}
})();