Greasy Fork is available in English.
Rewrite slow Bilibili playback CDN URLs for smoother overseas playback.
// ==UserScript==
// @name Bilibili Accelerator
// @name:zh-CN Bilibili Accelerator - B站海外播放加速
// @namespace https://github.com/realzza/bilibili-accelerator
// @version 0.1.2
// @description Rewrite slow Bilibili playback CDN URLs for smoother overseas playback.
// @description:zh-CN 自动改写 B 站慢 CDN 播放地址,缓解海外用户看冷门视频时的卡顿。
// @author realzza
// @license MIT
// @homepageURL https://github.com/realzza/bilibili-accelerator
// @supportURL https://github.com/realzza/bilibili-accelerator/issues
// @match https://*.bilibili.com/*
// @match https://*.bilibili.tv/*
// @run-at document-start
// @grant none
// ==/UserScript==
(function initBiliAcceleratorCore(root, factory) {
const core = factory();
if (typeof module === "object" && module.exports) {
module.exports = core;
}
root.BiliAcceleratorCore = core;
})(typeof globalThis !== "undefined" ? globalThis : window, function createCore() {
"use strict";
const DEFAULT_CONFIG = Object.freeze({
enabled: true,
mode: "bad-only",
pcdnHost: "upos-sz-mirrorcos.bilivideo.com",
mcdnStrategy: "proxy-all",
proxyHost: "proxy-tf-all-ws.bilivideo.com",
rewriteAkamai: false,
maxDepth: 20
});
const CDN_HOSTS = Object.freeze([
"upos-sz-mirrorcos.bilivideo.com",
"upos-sz-mirrorali.bilivideo.com",
"upos-sz-mirrorhw.bilivideo.com",
"upos-tf-all-hw.bilivideo.com",
"upos-tf-all-tx.bilivideo.com",
"upos-hz-mirrorakam.akamaized.net",
"upos-sz-mirrorakam.akamaized.net",
"upos-sz-mirroraliov.bilivideo.com",
"upos-sz-mirrorcosov.bilivideo.com",
"upos-sz-mirrorhwov.bilivideo.com"
]);
const MEDIA_PATH_RE = /\.(m4s|mp4|flv|m3u8)(?:$|[?#])/i;
const IP_RE = /^(?:\d{1,3}\.){3}\d{1,3}$/;
const XY_MCDN_RE = /^xy(?:\d+x){3}\d+xy\.mcdn\.bilivideo\.(?:cn|com|net)$/i;
function normalizeConfig(config) {
return Object.assign({}, DEFAULT_CONFIG, config || {});
}
function hasBiliMediaSignal(value) {
return typeof value === "string" &&
(value.includes("bilivideo") ||
value.includes("akamaized.net") ||
value.includes("szbdyd.com") ||
value.includes("/upgcxcode/") ||
value.includes("/v1/resource/"));
}
function parseUrl(value) {
if (!hasBiliMediaSignal(value)) {
return null;
}
try {
const url = new URL(value);
if (url.protocol !== "http:" && url.protocol !== "https:") {
return null;
}
return url;
} catch (_) {
return null;
}
}
function isMediaUrl(url) {
return MEDIA_PATH_RE.test(url.pathname + url.search) ||
url.pathname.startsWith("/upgcxcode/") ||
url.pathname.startsWith("/v1/resource/");
}
function isMcdnHost(hostname) {
return /\.mcdn\.bilivideo\.(?:cn|com|net)$/i.test(hostname);
}
function isPcdnHost(url) {
return IP_RE.test(url.hostname) || XY_MCDN_RE.test(url.hostname) || isMcdnHost(url.hostname);
}
function isBiliCdnHost(hostname) {
return hostname.endsWith(".bilivideo.com") ||
hostname.endsWith(".bilivideo.cn") ||
hostname.endsWith(".bilivideo.net") ||
hostname.endsWith(".akamaized.net");
}
function isKnownSlowHost(url, config) {
const hostname = url.hostname.toLowerCase();
if (isPcdnHost(url)) {
return true;
}
if (hostname.endsWith(".szbdyd.com")) {
return true;
}
if (hostname.includes("mirroraliov") || hostname.includes("mirrorcosov") || hostname.includes("mirrorhwov")) {
return true;
}
return config.rewriteAkamai && hostname.endsWith(".akamaized.net");
}
function cleanHost(host) {
const trimmed = String(host || "").trim();
return trimmed.replace(/^https?:\/\//i, "").replace(/\/.*$/, "");
}
function replaceHost(url, host) {
const next = new URL(url.toString());
next.protocol = "https:";
next.host = cleanHost(host);
if (!cleanHost(host).includes(":")) {
next.port = "";
}
return next.toString();
}
function proxyUrl(url, config) {
const next = new URL("https://" + cleanHost(config.proxyHost) + "/");
next.searchParams.set("url", url.toString());
return next.toString();
}
function shouldProxyMcdn(url, config) {
if (!isMcdnHost(url.hostname)) {
return false;
}
if (config.mcdnStrategy === "proxy-all") {
return true;
}
return config.mcdnStrategy === "proxy-v1" && url.pathname.startsWith("/v1/resource/");
}
function rewriteUrlDetail(value, rawConfig) {
const config = normalizeConfig(rawConfig);
const original = String(value || "");
const url = parseUrl(original);
if (!config.enabled || !url || !isMediaUrl(url) || url.hostname === cleanHost(config.proxyHost)) {
return {
changed: false,
original,
url: original,
reason: "ignored"
};
}
if (url.hostname.endsWith(".szbdyd.com")) {
const source = url.searchParams.get("xy_usource");
if (source) {
const rewritten = replaceHost(url, source);
return {
changed: rewritten !== original,
original,
url: rewritten,
reason: "szbdyd-source",
targetHost: cleanHost(source)
};
}
}
if (shouldProxyMcdn(url, config)) {
const rewritten = proxyUrl(url, config);
return {
changed: rewritten !== original,
original,
url: rewritten,
reason: "mcdn-proxy",
targetHost: cleanHost(config.proxyHost)
};
}
const force = config.mode === "force";
if (isKnownSlowHost(url, config) || (force && isBiliCdnHost(url.hostname))) {
const rewritten = replaceHost(url, config.pcdnHost);
return {
changed: rewritten !== original,
original,
url: rewritten,
reason: isPcdnHost(url) ? "pcdn-host" : "cdn-host",
targetHost: cleanHost(config.pcdnHost)
};
}
return {
changed: false,
original,
url: original,
reason: "ok"
};
}
function rewriteUrl(value, config) {
return rewriteUrlDetail(value, config).url;
}
function rewriteObject(value, rawConfig, state, depth, seen) {
const config = normalizeConfig(rawConfig);
const tracker = state || { changed: false, rewrites: [] };
const level = depth || 0;
const visited = seen || new WeakSet();
if (!config.enabled || value == null || level > config.maxDepth) {
return value;
}
if (typeof value === "string") {
const detail = rewriteUrlDetail(value, config);
if (detail.changed) {
tracker.changed = true;
tracker.rewrites.push(detail);
}
return detail.url;
}
if (typeof value !== "object") {
return value;
}
if (visited.has(value)) {
return value;
}
visited.add(value);
if (Array.isArray(value)) {
for (let index = 0; index < value.length; index += 1) {
value[index] = rewriteObject(value[index], config, tracker, level + 1, visited);
}
return value;
}
for (const key of Object.keys(value)) {
value[key] = rewriteObject(value[key], config, tracker, level + 1, visited);
}
return value;
}
function rewriteJsonText(text, rawConfig) {
const state = { changed: false, rewrites: [] };
const parsed = JSON.parse(text);
rewriteObject(parsed, rawConfig, state);
return {
changed: state.changed,
text: state.changed ? JSON.stringify(parsed) : text,
value: parsed,
rewrites: state.rewrites
};
}
return {
CDN_HOSTS,
DEFAULT_CONFIG,
normalizeConfig,
rewriteJsonText,
rewriteObject,
rewriteUrl,
rewriteUrlDetail
};
});
(function installBiliAccelerator(root) {
"use strict";
const core = root.BiliAcceleratorCore;
if (!core || root.__BILI_ACCELERATOR_INSTALLED__) {
return;
}
root.__BILI_ACCELERATOR_INSTALLED__ = true;
const STORAGE_KEY = "biliAccelerator.config.v1";
const PANEL_ID = "bili-accelerator-panel";
const BUTTON_ID = "bili-accelerator-button";
const nativeJsonParse = JSON.parse;
const state = {
rewrites: [],
rewriteCount: 0,
lastSource: "",
installedAt: new Date().toISOString()
};
function loadConfig() {
try {
const stored = root.localStorage.getItem(STORAGE_KEY);
return core.normalizeConfig(stored ? JSON.parse(stored) : null);
} catch (_) {
return core.normalizeConfig();
}
}
let config = loadConfig();
function saveConfig(nextConfig) {
config = core.normalizeConfig(nextConfig);
root.localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
}
function record(rewrites, source) {
if (!rewrites || rewrites.length === 0) {
return;
}
state.lastSource = source;
state.rewriteCount += rewrites.length;
state.rewrites = state.rewrites.concat(rewrites.map(function mapRewrite(item) {
return {
at: new Date().toISOString(),
source,
reason: item.reason,
targetHost: item.targetHost,
from: item.original,
to: item.url
};
})).slice(-50);
renderStatus();
}
function rewritePayload(payload, source) {
const tracker = { changed: false, rewrites: [] };
try {
const rewritten = core.rewriteObject(payload, config, tracker);
record(tracker.rewrites, source);
return rewritten;
} catch (error) {
console.warn("[BiliAccelerator] rewrite failed", error);
return payload;
}
}
function isInterestingFetch(input) {
const url = typeof input === "string" ? input : input && input.url;
return typeof url === "string" &&
(url.includes("/x/player") ||
url.includes("/pgc/player") ||
url.includes("playurl") ||
url.includes("bilivideo"));
}
function patchJsonParse() {
JSON.parse = function patchedJsonParse(text, reviver) {
const parsed = nativeJsonParse.apply(this, arguments);
if (typeof text === "string" && text.includes("bilivideo")) {
return rewritePayload(parsed, "JSON.parse");
}
return parsed;
};
}
function patchFetch() {
if (!root.fetch) {
return;
}
const nativeFetch = root.fetch;
root.fetch = function patchedFetch() {
const args = arguments;
return nativeFetch.apply(this, args).then(function handleResponse(response) {
if (!config.enabled || !isInterestingFetch(args[0])) {
return response;
}
const contentType = response.headers && response.headers.get("content-type");
if (contentType && !contentType.includes("json") && !contentType.includes("text")) {
return response;
}
return response.clone().text().then(function rewriteText(text) {
if (!text || !text.includes("bilivideo")) {
return response;
}
let parsed;
const tracker = { changed: false, rewrites: [] };
try {
parsed = nativeJsonParse(text);
core.rewriteObject(parsed, config, tracker);
} catch (_) {
return response;
}
if (!tracker.changed) {
return response;
}
record(tracker.rewrites, "fetch");
const headers = new Headers(response.headers);
headers.delete("content-length");
return new Response(JSON.stringify(parsed), {
status: response.status,
statusText: response.statusText,
headers
});
}).catch(function ignoreRewriteError() {
return response;
});
});
};
}
function patchGlobalPlayInfo(name) {
let currentValue;
const existing = Object.getOwnPropertyDescriptor(root, name);
if (existing && existing.configurable === false) {
return;
}
if (existing && "value" in existing) {
currentValue = rewritePayload(existing.value, name);
}
try {
Object.defineProperty(root, name, {
configurable: true,
enumerable: true,
get: function getPlayInfo() {
return currentValue;
},
set: function setPlayInfo(value) {
currentValue = rewritePayload(value, name);
}
});
} catch (_) {
if (root[name]) {
root[name] = rewritePayload(root[name], name);
}
}
}
function makeOption(value, label, selectedValue) {
const option = document.createElement("option");
option.value = value;
option.textContent = label;
option.selected = value === selectedValue;
return option;
}
function createSelect(options, value, onChange) {
const select = document.createElement("select");
select.className = "ba-control";
select.addEventListener("change", function handleChange() {
onChange(select.value);
});
options.forEach(function addOption(option) {
select.appendChild(makeOption(option.value, option.label, value));
});
return select;
}
function createField(labelText, control) {
const label = document.createElement("label");
label.className = "ba-field";
const caption = document.createElement("span");
caption.textContent = labelText;
label.appendChild(caption);
label.appendChild(control);
return label;
}
function renderStatus() {
const host = document.getElementById(BUTTON_ID);
const status = host && host.shadowRoot && host.shadowRoot.getElementById("ba-status");
const count = host && host.shadowRoot && host.shadowRoot.getElementById("ba-count");
const indicator = host && host.shadowRoot && host.shadowRoot.getElementById("ba-indicator");
if (!status) {
return;
}
const last = state.rewrites[state.rewrites.length - 1];
if (count) {
count.textContent = String(state.rewriteCount);
}
if (indicator) {
indicator.textContent = config.enabled ? "On" : "Off";
indicator.className = config.enabled ? "ba-pill is-on" : "ba-pill";
}
status.textContent = last
? "Last rewrite: " + last.reason + " -> " + last.targetHost
: "Waiting for Bilibili playback URLs.";
}
function installUi() {
if (!document.documentElement || document.getElementById(BUTTON_ID)) {
return;
}
const host = document.createElement("div");
host.id = BUTTON_ID;
const shadow = host.attachShadow({ mode: "open" });
const style = document.createElement("style");
style.textContent = [
":host{position:fixed;right:18px;bottom:18px;z-index:2147483647;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;color:#17202a}",
"*{box-sizing:border-box}",
"button,input,select{font:inherit}",
".ba-toggle{display:inline-flex;align-items:center;gap:7px;height:36px;min-width:72px;border:1px solid rgba(23,32,42,.14);border-radius:999px;background:rgba(255,255,255,.92);color:#17202a;box-shadow:0 8px 24px rgba(21,32,43,.18),0 1px 0 rgba(255,255,255,.9) inset;backdrop-filter:saturate(180%) blur(14px);-webkit-backdrop-filter:saturate(180%) blur(14px);cursor:pointer;font-weight:700;padding:0 12px 0 9px;transition:transform .16s ease,box-shadow .16s ease,background .16s ease}",
".ba-toggle:hover{transform:translateY(-1px);box-shadow:0 12px 30px rgba(21,32,43,.22),0 1px 0 rgba(255,255,255,.9) inset;background:#fff}",
".ba-toggle:active{transform:translateY(0)}",
".ba-mark{display:grid;place-items:center;width:22px;height:22px;border-radius:999px;background:#00aeec;color:#fff;box-shadow:0 4px 10px rgba(0,174,236,.34)}",
".ba-mark svg{width:14px;height:14px;display:block}",
".ba-toggle-text{font-size:12px;letter-spacing:0}",
".ba-panel{display:none;position:absolute;right:0;bottom:48px;width:min(340px,calc(100vw - 36px));padding:14px;border:1px solid rgba(23,32,42,.12);border-radius:12px;background:rgba(255,255,255,.96);box-shadow:0 18px 46px rgba(21,32,43,.24);backdrop-filter:saturate(180%) blur(18px);-webkit-backdrop-filter:saturate(180%) blur(18px)}",
".ba-panel.open{display:block}",
".ba-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}",
".ba-title{font-size:14px;font-weight:800;margin:0;line-height:1.25;color:#111827}",
".ba-subtitle{font-size:11px;line-height:1.35;color:#5b6773;margin:3px 0 0}",
".ba-pill{display:inline-flex;align-items:center;height:22px;border-radius:999px;background:#eef2f6;color:#5b6773;font-size:11px;font-weight:700;padding:0 8px;white-space:nowrap}",
".ba-pill.is-on{background:#e6f8ff;color:#0077a3}",
".ba-stats{display:grid;grid-template-columns:84px 1fr;gap:10px;align-items:center;border:1px solid #e5eaf0;border-radius:10px;background:#f7fafc;padding:10px;margin-bottom:12px}",
".ba-count{font-size:22px;line-height:1;font-weight:800;color:#00aeec;text-align:center}",
".ba-count-label{display:block;font-size:10px;font-weight:700;color:#6b7785;text-transform:uppercase;letter-spacing:.04em;margin-top:3px;text-align:center}",
"#ba-status{font-size:11px;line-height:1.45;color:#34495e;word-break:break-word}",
".ba-field{display:grid;grid-template-columns:88px 1fr;align-items:center;gap:9px;margin:9px 0;font-size:12px}",
".ba-field span{color:#46515c;font-weight:650}",
".ba-control,.ba-field input[type=text],.ba-field select{width:100%;min-width:0;height:32px;border:1px solid #d5dde5;border-radius:8px;padding:0 9px;background:#fff;color:#17202a;outline:none;font-size:11px}",
".ba-control:focus,.ba-field input[type=text]:focus{border-color:#00aeec;box-shadow:0 0 0 3px rgba(0,174,236,.14)}",
".ba-switch-row{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:10px 0;padding:9px 10px;border:1px solid #e5eaf0;border-radius:10px;background:#fff}",
".ba-switch-text{display:grid;gap:2px}",
".ba-switch-title{font-size:12px;font-weight:750;color:#202a33}",
".ba-switch-note{font-size:11px;color:#6b7785;line-height:1.3}",
".ba-switch{position:relative;display:inline-flex;width:42px;height:24px;flex:0 0 auto}",
".ba-switch input{position:absolute;opacity:0;width:1px;height:1px}",
".ba-slider{position:absolute;inset:0;border-radius:999px;background:#c9d3dd;cursor:pointer;transition:background .16s ease}",
".ba-slider:before{content:'';position:absolute;width:20px;height:20px;left:2px;top:2px;border-radius:50%;background:#fff;box-shadow:0 2px 6px rgba(0,0,0,.22);transition:transform .16s ease}",
".ba-switch input:checked+.ba-slider{background:#00aeec}",
".ba-switch input:checked+.ba-slider:before{transform:translateX(18px)}",
".ba-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}",
".ba-actions button{height:32px;border:1px solid #d5dde5;border-radius:8px;background:#fff;color:#25313d;padding:0 11px;cursor:pointer;font-size:12px;font-weight:700}",
".ba-actions button.primary{border-color:#00aeec;background:#00aeec;color:#fff}",
".ba-note{font-size:11px;line-height:1.4;color:#6b7785;margin:10px 0 0}"
].join("");
const toggle = document.createElement("button");
toggle.className = "ba-toggle";
toggle.type = "button";
toggle.title = "Bilibili Accelerator";
const mark = document.createElement("span");
mark.className = "ba-mark";
mark.innerHTML = "<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path fill=\"currentColor\" d=\"M13 2 4 14h7l-1 8 10-13h-7l1-7Z\"/></svg>";
const toggleText = document.createElement("span");
toggleText.className = "ba-toggle-text";
toggleText.textContent = "Bili";
toggle.appendChild(mark);
toggle.appendChild(toggleText);
const panel = document.createElement("section");
panel.className = "ba-panel";
panel.id = PANEL_ID;
const head = document.createElement("div");
head.className = "ba-head";
const headText = document.createElement("div");
const title = document.createElement("p");
title.className = "ba-title";
title.textContent = "Bilibili Accelerator";
const subtitle = document.createElement("p");
subtitle.className = "ba-subtitle";
subtitle.textContent = "Playback CDN rewrite";
const indicator = document.createElement("span");
indicator.id = "ba-indicator";
indicator.className = config.enabled ? "ba-pill is-on" : "ba-pill";
indicator.textContent = config.enabled ? "On" : "Off";
headText.appendChild(title);
headText.appendChild(subtitle);
head.appendChild(headText);
head.appendChild(indicator);
const stats = document.createElement("div");
stats.className = "ba-stats";
const countBox = document.createElement("div");
const countValue = document.createElement("div");
countValue.className = "ba-count";
countValue.id = "ba-count";
countValue.textContent = String(state.rewriteCount);
const countLabel = document.createElement("span");
countLabel.className = "ba-count-label";
countLabel.textContent = "rewrites";
countBox.appendChild(countValue);
countBox.appendChild(countLabel);
const status = document.createElement("div");
status.id = "ba-status";
stats.appendChild(countBox);
stats.appendChild(status);
const enabled = document.createElement("input");
enabled.type = "checkbox";
enabled.checked = config.enabled;
enabled.addEventListener("change", function handleEnabled() {
saveConfig(Object.assign({}, config, { enabled: enabled.checked }));
renderStatus();
});
const enabledSwitch = document.createElement("span");
enabledSwitch.className = "ba-switch";
const enabledSlider = document.createElement("span");
enabledSlider.className = "ba-slider";
enabledSwitch.appendChild(enabled);
enabledSwitch.appendChild(enabledSlider);
const enabledRow = document.createElement("label");
enabledRow.className = "ba-switch-row";
const enabledCopy = document.createElement("span");
enabledCopy.className = "ba-switch-text";
const enabledTitle = document.createElement("span");
enabledTitle.className = "ba-switch-title";
enabledTitle.textContent = "Enabled";
const enabledNote = document.createElement("span");
enabledNote.className = "ba-switch-note";
enabledNote.textContent = "Rewrite slow playback hosts before buffering.";
enabledCopy.appendChild(enabledTitle);
enabledCopy.appendChild(enabledNote);
enabledRow.appendChild(enabledCopy);
enabledRow.appendChild(enabledSwitch);
const mode = createSelect([
{ value: "bad-only", label: "Bad CDN only" },
{ value: "force", label: "Force all video CDN" }
], config.mode, function handleMode(value) {
saveConfig(Object.assign({}, config, { mode: value }));
});
const mcdn = createSelect([
{ value: "proxy-all", label: "Proxy all MCDN" },
{ value: "proxy-v1", label: "Proxy /v1 only" },
{ value: "replace", label: "Replace host" }
], config.mcdnStrategy, function handleMcdn(value) {
saveConfig(Object.assign({}, config, { mcdnStrategy: value }));
});
const hostInput = document.createElement("input");
hostInput.type = "text";
hostInput.className = "ba-control";
hostInput.value = config.pcdnHost;
hostInput.setAttribute("list", "ba-hosts");
hostInput.addEventListener("change", function handleHost() {
saveConfig(Object.assign({}, config, { pcdnHost: hostInput.value }));
});
const hostList = document.createElement("datalist");
hostList.id = "ba-hosts";
core.CDN_HOSTS.forEach(function addHost(host) {
const option = document.createElement("option");
option.value = host;
hostList.appendChild(option);
});
const akamai = document.createElement("input");
akamai.type = "checkbox";
akamai.checked = config.rewriteAkamai;
akamai.addEventListener("change", function handleAkamai() {
saveConfig(Object.assign({}, config, { rewriteAkamai: akamai.checked }));
});
const akamaiSwitch = document.createElement("span");
akamaiSwitch.className = "ba-switch";
const akamaiSlider = document.createElement("span");
akamaiSlider.className = "ba-slider";
akamaiSwitch.appendChild(akamai);
akamaiSwitch.appendChild(akamaiSlider);
const akamaiRow = document.createElement("label");
akamaiRow.className = "ba-switch-row";
const akamaiCopy = document.createElement("span");
akamaiCopy.className = "ba-switch-text";
const akamaiTitle = document.createElement("span");
akamaiTitle.className = "ba-switch-title";
akamaiTitle.textContent = "Rewrite Akamai";
const akamaiNote = document.createElement("span");
akamaiNote.className = "ba-switch-note";
akamaiNote.textContent = "Use only if Akamai is slow on your network.";
akamaiCopy.appendChild(akamaiTitle);
akamaiCopy.appendChild(akamaiNote);
akamaiRow.appendChild(akamaiCopy);
akamaiRow.appendChild(akamaiSwitch);
const note = document.createElement("p");
note.className = "ba-note";
note.textContent = "Change settings, then reload the video page.";
const reload = document.createElement("button");
reload.type = "button";
reload.className = "primary";
reload.textContent = "Reload";
reload.addEventListener("click", function handleReload() {
root.location.reload();
});
const close = document.createElement("button");
close.type = "button";
close.textContent = "Close";
close.addEventListener("click", function handleClose() {
panel.classList.remove("open");
});
const actions = document.createElement("div");
actions.className = "ba-actions";
actions.appendChild(reload);
actions.appendChild(close);
panel.appendChild(head);
panel.appendChild(stats);
panel.appendChild(enabledRow);
panel.appendChild(createField("Mode", mode));
panel.appendChild(createField("Target host", hostInput));
panel.appendChild(hostList);
panel.appendChild(createField("MCDN", mcdn));
panel.appendChild(akamaiRow);
panel.appendChild(note);
panel.appendChild(actions);
toggle.addEventListener("click", function handleToggle() {
panel.classList.toggle("open");
renderStatus();
});
shadow.appendChild(style);
shadow.appendChild(panel);
shadow.appendChild(toggle);
document.documentElement.appendChild(host);
renderStatus();
}
root.BiliAccelerator = {
getConfig: function getConfig() {
return Object.assign({}, config);
},
setConfig: function setConfig(nextConfig) {
saveConfig(Object.assign({}, config, nextConfig || {}));
return this.getConfig();
},
getStats: function getStats() {
return JSON.parse(JSON.stringify(state));
},
rewriteUrl: function rewritePublicUrl(url) {
return core.rewriteUrl(url, config);
}
};
patchJsonParse();
patchFetch();
patchGlobalPlayInfo("__playinfo__");
patchGlobalPlayInfo("__INITIAL_STATE__");
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", installUi, { once: true });
} else {
installUi();
}
console.info("[BiliAccelerator] installed", root.BiliAccelerator.getConfig());
})(typeof globalThis !== "undefined" ? globalThis : window);