const TARGET = "BV1HA3o6oEJJ";
|
const CANONICAL_URL = "https://www.bilibili.com/video/BV1HA3o6oEJJ";
|
const TARGET_PATH = "/video/BV1HA3o6oEJJ";
|
const HOST = "com.project_info.bili_auth_ingress";
|
const SCHEMA = 2;
|
const EXTENSION_BUILD = "project-info-bili-auth-ingress/1.0.0+20260805.v002";
|
const HOST_BUILD = "project-info-bili-auth-native-host/1.0.0+20260805.v002";
|
const SIDEPANEL_URL = chrome.runtime.getURL("sidepanel.html");
|
const EXPECTED_DURATION_MS = 3133950;
|
const DURATION_TOLERANCE_MS = 3134;
|
|
let currentProof = null;
|
let currentTabId = null;
|
let currentTaskNonce = null;
|
let nativePort = null;
|
let nativeReady = false;
|
let nativeRevision = 0;
|
let lastNativeType = null;
|
let preparedTaskNonce = null;
|
let preparedLeaseId = null;
|
let activeStartLease = null;
|
const prepareResponses = new Map();
|
let safeState = {
|
phase: "IDLE",
|
progress: 0,
|
error_code: null,
|
formal_filename: null,
|
mapping_filename: null
|
};
|
|
chrome.runtime.onInstalled.addListener(() => {
|
chrome.sidePanel.setPanelBehavior({openPanelOnActionClick: true});
|
});
|
|
function randomNonce() {
|
const bytes = new Uint8Array(16);
|
crypto.getRandomValues(bytes);
|
return Array.from(bytes, value => value.toString(16).padStart(2, "0")).join("");
|
}
|
|
function fixedError(code) {
|
const error = new Error(code);
|
error.code = code;
|
return error;
|
}
|
|
async function activeTargetTab() {
|
const tabs = await chrome.tabs.query({active: true, currentWindow: true});
|
if (tabs.length !== 1 || !Number.isInteger(tabs[0].id)) {
|
throw fixedError("E_PAGE_PROOF");
|
}
|
const parsed = new URL(tabs[0].url || "about:blank");
|
if (parsed.protocol !== "https:" || parsed.hostname !== "www.bilibili.com" || parsed.pathname !== TARGET_PATH) {
|
throw fixedError("E_PAGE_PROOF");
|
}
|
return tabs[0];
|
}
|
|
async function observePage(tab, nonce) {
|
const results = await chrome.scripting.executeScript({
|
target: {tabId: tab.id},
|
world: "ISOLATED",
|
args: [TARGET, CANONICAL_URL, nonce],
|
func: (target, canonicalUrl, taskNonce) => {
|
const videos = Array.from(document.querySelectorAll("video")).filter(video =>
|
video.readyState >= 1 && video.videoWidth > 0 && video.videoHeight > 0
|
);
|
if (location.hostname !== "www.bilibili.com" || location.pathname !== `/video/${target}` || videos.length !== 1) {
|
return null;
|
}
|
const video = videos[0];
|
return {
|
target,
|
canonical_url: canonicalUrl,
|
task_nonce: taskNonce,
|
observed_at_unix_ms: Date.now(),
|
observed_duration_ms: Math.round(video.duration * 1000),
|
video_width: video.videoWidth,
|
video_height: video.videoHeight,
|
ready_state: video.readyState,
|
eme_present: video.mediaKeys !== null
|
};
|
}
|
});
|
if (results.length !== 1 || !results[0].result) {
|
throw fixedError("E_PAGE_PROOF");
|
}
|
const proof = results[0].result;
|
const now = Date.now();
|
const exactKeys = [
|
"canonical_url", "eme_present", "observed_at_unix_ms", "observed_duration_ms",
|
"ready_state", "target", "task_nonce", "video_height", "video_width"
|
];
|
if (Object.keys(proof).sort().join("|") !== exactKeys.sort().join("|") ||
|
proof.target !== TARGET || proof.canonical_url !== CANONICAL_URL ||
|
proof.task_nonce !== nonce || proof.eme_present !== false ||
|
!Number.isSafeInteger(proof.observed_at_unix_ms) ||
|
proof.observed_at_unix_ms < now - 60000 || proof.observed_at_unix_ms > now + 5000 ||
|
!Number.isSafeInteger(proof.observed_duration_ms) ||
|
Math.abs(proof.observed_duration_ms - EXPECTED_DURATION_MS) > DURATION_TOLERANCE_MS ||
|
!Number.isInteger(proof.video_width) || proof.video_width < 1 || proof.video_width > 7680 ||
|
!Number.isInteger(proof.video_height) || proof.video_height < 1 || proof.video_height > 4320 ||
|
!Number.isInteger(proof.ready_state) || proof.ready_state < 1 || proof.ready_state > 4) {
|
throw fixedError(proof.eme_present ? "E_DRM" : "E_PAGE_PROOF");
|
}
|
return proof;
|
}
|
|
async function validateCurrentPage({leaseId = null} = {}) {
|
if (activeStartLease !== null && activeStartLease !== leaseId) {
|
throw fixedError("E_BUSY");
|
}
|
const tab = await activeTargetTab();
|
const nonce = randomNonce();
|
const proof = await observePage(tab, nonce);
|
currentProof = proof;
|
currentTabId = tab.id;
|
if (nativePort) {
|
const previousPort = nativePort;
|
nativePort = null;
|
nativeReady = false;
|
preparedTaskNonce = null;
|
preparedLeaseId = null;
|
prepareResponses.clear();
|
previousPort.disconnect();
|
}
|
await awaitNativeReady();
|
safeState = {...safeState, phase: "READY", error_code: null};
|
return safeState;
|
}
|
|
function connectNative() {
|
if (nativePort) {
|
return;
|
}
|
nativeReady = false;
|
preparedTaskNonce = null;
|
preparedLeaseId = null;
|
prepareResponses.clear();
|
const port = chrome.runtime.connectNative(HOST);
|
nativePort = port;
|
port.onMessage.addListener(message => {
|
const safeKeys = new Set([
|
"schema", "type", "host_build", "target", "phase", "progress", "error_code",
|
"formal_filename", "mapping_filename", "prepare_id"
|
]);
|
const basename = value => value === null || (typeof value === "string" && value.length > 0 && !/[\\/]/.test(value));
|
const prepareIdValid = message?.prepare_id === null ||
|
(typeof message?.prepare_id === "string" && /^[0-9a-f]{32}$/.test(message.prepare_id));
|
if (!message || Object.keys(message).some(key => !safeKeys.has(key)) ||
|
message.schema !== SCHEMA || message.host_build !== HOST_BUILD || message.target !== TARGET ||
|
!["IDLE", "READY", "CHECKING", "DOWNLOADING", "MERGING", "VALIDATING", "PUBLISHING", "COMPLETE", "FAILED", "CANCELED"].includes(message.phase) ||
|
!Number.isInteger(message.progress) || message.progress < 0 || message.progress > 100 ||
|
!basename(message.formal_filename) || !basename(message.mapping_filename) || !prepareIdValid ||
|
(message.type === "prepare" ? message.prepare_id === null : message.prepare_id !== null)) {
|
safeState = {...safeState, phase: "FAILED", error_code: "E_PROTOCOL"};
|
return;
|
}
|
nativeReady = nativeReady || (message.type === "hello" && message.phase === "READY");
|
lastNativeType = message.type;
|
if (message.type === "prepare") {
|
prepareResponses.set(message.prepare_id, {
|
phase: message.phase,
|
error_code: message.error_code
|
});
|
}
|
safeState = {
|
phase: message.phase,
|
progress: message.progress,
|
error_code: message.error_code,
|
formal_filename: message.formal_filename,
|
mapping_filename: message.mapping_filename
|
};
|
nativeRevision += 1;
|
});
|
port.onDisconnect.addListener(() => {
|
if (nativePort === port) {
|
nativePort = null;
|
nativeReady = false;
|
preparedTaskNonce = null;
|
preparedLeaseId = null;
|
prepareResponses.clear();
|
if (!["COMPLETE", "FAILED", "CANCELED"].includes(safeState.phase)) {
|
safeState = {...safeState, phase: "FAILED", error_code: "E_NATIVE_DISCONNECT"};
|
}
|
}
|
});
|
port.postMessage({
|
schema: SCHEMA,
|
type: "hello",
|
extension_build: EXTENSION_BUILD,
|
target: TARGET
|
});
|
}
|
|
async function freshNativeStatus() {
|
const prior = nativeRevision;
|
nativePort.postMessage({schema: SCHEMA, type: "status", target: TARGET});
|
const deadline = Date.now() + 5000;
|
while (nativeRevision === prior && Date.now() < deadline) {
|
await new Promise(resolve => setTimeout(resolve, 25));
|
}
|
if (nativeRevision === prior) {
|
throw fixedError("E_NATIVE_CONNECT");
|
}
|
return safeState;
|
}
|
|
async function awaitNativeReady() {
|
connectNative();
|
const deadline = Date.now() + 5000;
|
while (!nativeReady && Date.now() < deadline) {
|
if (safeState.error_code) {
|
throw fixedError(safeState.error_code);
|
}
|
await new Promise(resolve => setTimeout(resolve, 25));
|
}
|
if (!nativeReady) {
|
throw fixedError("E_NATIVE_CONNECT");
|
}
|
}
|
|
async function freshNativePrepare(proof, leaseId) {
|
await awaitNativeReady();
|
if (activeStartLease !== leaseId) {
|
throw fixedError("E_BUSY");
|
}
|
preparedTaskNonce = null;
|
preparedLeaseId = null;
|
const prepareId = randomNonce();
|
prepareResponses.delete(prepareId);
|
nativePort.postMessage({
|
schema: SCHEMA,
|
type: "prepare",
|
extension_build: EXTENSION_BUILD,
|
target: TARGET,
|
prepare_id: prepareId,
|
page_proof: proof
|
});
|
const deadline = Date.now() + 120000;
|
while (!prepareResponses.has(prepareId) && Date.now() < deadline) {
|
await new Promise(resolve => setTimeout(resolve, 25));
|
}
|
const response = prepareResponses.get(prepareId);
|
prepareResponses.delete(prepareId);
|
if (!response || response.phase !== "READY" || response.error_code) {
|
throw fixedError(response?.error_code || "E_PREPARE");
|
}
|
preparedTaskNonce = proof.task_nonce;
|
preparedLeaseId = leaseId;
|
return prepareId;
|
}
|
|
async function currentCookieStore(tabId) {
|
const stores = await chrome.cookies.getAllCookieStores();
|
const matches = stores.filter(store => Array.isArray(store.tabIds) && store.tabIds.includes(tabId));
|
if (matches.length !== 1 || !/^[0-9]{1,8}$/.test(matches[0].id)) {
|
throw fixedError("E_SECRET_INPUT");
|
}
|
return matches[0].id;
|
}
|
|
function projectCookie(cookie, storeId) {
|
if (cookie.partitionKey !== undefined) {
|
throw fixedError("E_SECRET_INPUT");
|
}
|
if (!cookie.name || !cookie.value) {
|
return null;
|
}
|
const session = Boolean(cookie.session);
|
const expiration = session ? null : Math.floor(cookie.expirationDate);
|
if (!session && (!Number.isSafeInteger(expiration) || expiration <= 0)) {
|
throw fixedError("E_SECRET_INPUT");
|
}
|
return {
|
name: String(cookie.name),
|
value: String(cookie.value),
|
domain: String(cookie.domain),
|
host_only: Boolean(cookie.hostOnly),
|
path: String(cookie.path),
|
secure: Boolean(cookie.secure),
|
http_only: Boolean(cookie.httpOnly),
|
same_site: String(cookie.sameSite || "unspecified"),
|
session,
|
expiration_unix: expiration,
|
store_id: storeId,
|
partition_key: null
|
};
|
}
|
|
async function startExactTask({retry = false} = {}) {
|
if (activeStartLease !== null) {
|
throw fixedError("E_BUSY");
|
}
|
const leaseId = randomNonce();
|
activeStartLease = leaseId;
|
try {
|
if (retry) {
|
await validateCurrentPage({leaseId});
|
}
|
const tab = await activeTargetTab();
|
if (!currentProof || currentTabId !== tab.id || Date.now() - currentProof.observed_at_unix_ms > 60000) {
|
throw fixedError("E_PAGE_PROOF");
|
}
|
const proof = currentProof;
|
const prepareId = await freshNativePrepare(proof, leaseId);
|
if (activeStartLease !== leaseId || preparedLeaseId !== leaseId || preparedTaskNonce !== proof.task_nonce) {
|
throw fixedError("E_PREPARE");
|
}
|
const storeId = await currentCookieStore(tab.id);
|
const rawCookies = await chrome.cookies.getAll({url: "https://www.bilibili.com/", storeId});
|
let records = [];
|
try {
|
records = rawCookies.map(cookie => projectCookie(cookie, storeId)).filter(Boolean);
|
if (records.length < 1 || records.length > 128) {
|
throw fixedError("E_SECRET_INPUT");
|
}
|
nativePort.postMessage({
|
schema: SCHEMA,
|
type: "start",
|
extension_build: EXTENSION_BUILD,
|
target: TARGET,
|
canonical_url: CANONICAL_URL,
|
cookie_store_id: storeId,
|
prepare_id: prepareId,
|
page_proof: proof,
|
cookies: records
|
});
|
currentTaskNonce = proof.task_nonce;
|
preparedTaskNonce = null;
|
preparedLeaseId = null;
|
safeState = {...safeState, phase: "CHECKING", progress: 0, error_code: null};
|
currentProof = null;
|
currentTabId = null;
|
return safeState;
|
} finally {
|
for (const record of records) {
|
if (record) {
|
record.name = "";
|
record.value = "";
|
}
|
}
|
records.fill(null);
|
rawCookies.fill(null);
|
}
|
} finally {
|
if (activeStartLease === leaseId) {
|
activeStartLease = null;
|
}
|
}
|
}
|
|
async function requestStatus() {
|
await awaitNativeReady();
|
return freshNativeStatus();
|
}
|
|
async function cancelTask() {
|
if (!currentTaskNonce) {
|
throw fixedError("E_CANCEL");
|
}
|
await awaitNativeReady();
|
nativePort.postMessage({schema: SCHEMA, type: "cancel", target: TARGET, task_nonce: currentTaskNonce});
|
return safeState;
|
}
|
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
if (sender.id !== chrome.runtime.id || sender.url !== SIDEPANEL_URL || !message || typeof message.action !== "string") {
|
return false;
|
}
|
const actions = {
|
validate: () => validateCurrentPage(),
|
start: () => startExactTask(),
|
retry: () => startExactTask({retry: true}),
|
status: () => requestStatus(),
|
cancel: () => cancelTask()
|
};
|
const action = actions[message.action];
|
if (!action) {
|
sendResponse({ok: false, error_code: "E_PROTOCOL"});
|
return false;
|
}
|
action().then(
|
state => sendResponse({ok: true, state}),
|
error => sendResponse({ok: false, error_code: error.code || "E_EXTENSION"})
|
);
|
return true;
|
});
|