Add download video and save playlist support.

This commit is contained in:
wei
2026-08-25 17:46:48 +08:00
parent eadc5c6267
commit 04e03c132d
19 changed files with 2514 additions and 1386 deletions
+124 -31
View File
@@ -1,34 +1,127 @@
function bytesToBase64Url(bytes) {
let binary = "";
for (let offset = 0; offset < bytes.length; offset += 8192) {
binary += String.fromCharCode(...bytes.subarray(offset, offset + 8192));
}
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
}
function base64UrlToBytes(value) {
const padded = value.replaceAll("-", "+").replaceAll("_", "/")
+ "=".repeat((4 - value.length % 4) % 4);
return Uint8Array.from(atob(padded), character => character.charCodeAt(0));
}
async function hmacSha256(keyBytes, data) {
const key = await crypto.subtle.importKey(
"raw", keyBytes, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]
);
return new Uint8Array(await crypto.subtle.sign("HMAC", key, data));
}
function joinBytes(...arrays) {
const result = new Uint8Array(arrays.reduce((size, array) => size + array.length, 0));
let offset = 0;
for (const array of arrays) {
result.set(array, offset);
offset += array.length;
}
return result;
}
async function encryptCookies(plaintext, secretKey) {
const masterKey = base64UrlToBytes(secretKey);
if (masterKey.length !== 32) {
throw new Error("The Helper secret key is invalid.");
}
const encoder = new TextEncoder();
const encryptionKey = await hmacSha256(masterKey, encoder.encode("PlaylistSaver cookie encryption"));
const authenticationKey = await hmacSha256(masterKey, encoder.encode("PlaylistSaver cookie authentication"));
const nonce = crypto.getRandomValues(new Uint8Array(16));
const plaintextBytes = encoder.encode(plaintext);
const ciphertext = new Uint8Array(plaintextBytes.length);
for (let offset = 0; offset < plaintextBytes.length; offset += 32) {
const counter = new Uint8Array(4);
new DataView(counter.buffer).setUint32(0, offset / 32, false);
const stream = await hmacSha256(encryptionKey, joinBytes(nonce, counter));
const blockLength = Math.min(32, plaintextBytes.length - offset);
for (let index = 0; index < blockLength; index++) {
ciphertext[offset + index] = plaintextBytes[offset + index] ^ stream[index];
}
}
const signedData = joinBytes(Uint8Array.of(1), nonce, ciphertext);
const tag = await hmacSha256(authenticationKey, signedData);
return bytesToBase64Url(joinBytes(signedData, tag));
}
async function launchPlaylistSaver() {
const { secretKey } = await chrome.storage.local.get("secretKey");
if (!secretKey) {
await chrome.storage.local.set({ showMissingKeyAlert: true });
await chrome.runtime.openOptionsPage();
return;
}
const cookies = await chrome.cookies.getAll({ domain: ".youtube.com" });
let output = "# Netscape HTTP Cookie File\n";
for (const cookie of cookies) {
const rawDomain = cookie.domain;
const domain = cookie.httpOnly ? `#HttpOnly_${rawDomain}` : rawDomain;
output += [
domain,
rawDomain.startsWith(".") ? "TRUE" : "FALSE",
cookie.path,
cookie.secure ? "TRUE" : "FALSE",
cookie.expirationDate ? Math.floor(cookie.expirationDate) : 0,
cookie.name,
cookie.value
].join("\t") + "\n";
}
const encrypted = await encryptCookies(output, secretKey.trim());
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tabs[0]?.id) {
throw new Error("No active tab is available to launch PlaylistSaver.");
}
await chrome.tabs.update(tabs[0].id, {
url: `playlistsaver://open?encryptedcookies=${encrypted}`
});
}
async function openHelperSettings() {
await chrome.runtime.openOptionsPage();
}
chrome.commands.onCommand.addListener((command) => {
if (command === "openPLKey") {
chrome.cookies.getAll({ domain: ".youtube.com" }, (cookies) => {
let output = "# Netscape HTTP Cookie File\n";
cookies.forEach(c => {
const domain = c.domain;
const flag = domain.startsWith('.') ? "TRUE" : "FALSE";
const path = c.path;
const secure = c.secure ? "TRUE" : "FALSE";
const expiration = c.expirationDate ? Math.floor(c.expirationDate) : 0;
output += [
domain,
flag,
path,
secure,
expiration,
c.name,
c.value
].join("\t") + "\n";
});
const encoded = btoa(output);
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.update(tabs[0].id, {
url: `PlaylistSaver://open?base64cookies=${encoded}`
});
});
});
launchPlaylistSaver().catch(error => console.error("Unable to launch PlaylistSaver:", error));
} else if (command === "openPLSettingPageKey") {
openHelperSettings().catch(error => console.error("Unable to open Helper settings:", error));
}
});
});
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.removeAll(() => {
chrome.contextMenus.create({
id: "openPlaylistSaver",
title: "Open PlaylistSaver",
contexts: ["page"]
});
chrome.contextMenus.create({
id: "openHelperSettings",
title: "PlaylistSaver Helper Settings",
contexts: ["page"]
});
});
});
chrome.contextMenus.onClicked.addListener((info) => {
if (info.menuItemId === "openPlaylistSaver") {
launchPlaylistSaver().catch(error => console.error("Unable to launch PlaylistSaver:", error));
} else if (info.menuItemId === "openHelperSettings") {
openHelperSettings().catch(error => console.error("Unable to open Helper settings:", error));
}
});