read-browser-cookies
Version:
Node.js version of `--cookies-from-browser` from yt-dlp
197 lines (196 loc) • 7.68 kB
JavaScript
;
/**
* https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/cookies.py
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.processChromeTimestamp = exports.readChromium = exports.chromiumBasedBrowsers = void 0;
const fs_1 = __importDefault(require("fs"));
const globby_1 = __importDefault(require("globby"));
const lodash_1 = require("lodash");
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const psl_1 = __importDefault(require("psl"));
const db_1 = require("../db");
const helper_1 = require("../helper");
const decrypt_1 = require("./decrypt");
const debug = helper_1.baseDebug.extend('chromium:index');
exports.chromiumBasedBrowsers = [
'brave',
'chrome',
'chromium',
'edge',
'opera',
'vivaldi',
];
// cookiesfrombrowser: A tuple containing
// - the name of the browser, the profile
// - name/path from where cookies are loaded
// - the name of the keyring,
// - and the container name,
// e.g.
// ('chrome', )
// or ('vivaldi', 'default', 'BASICTEXT')
// or ('firefox', 'default', None, 'Meta')
async function readChromium(browserName, { profile, keyring, site } = {}) {
const config = getChromiumBasedBrowserSettings(browserName);
let searchRoot;
// no profile
if (!profile) {
searchRoot = config.browserDir;
}
// profile is path
else if (isPath(profile)) {
searchRoot = profile;
config.browserDir = config.supportsProfiles ? path_1.default.dirname(profile) : profile;
}
// profile is normal string
else {
if (config.supportsProfiles) {
searchRoot = path_1.default.join(config.browserDir, profile);
}
else {
console.error('%s does not support profiles');
searchRoot = config.browserDir;
}
}
// check search_root
if (!fs_1.default.existsSync(searchRoot)) {
debug('search root: %s not exists', searchRoot);
return;
}
const cookieDatabasePath = findMostRecentlyUsedFile(searchRoot, 'Cookies');
if (!cookieDatabasePath) {
debug('cookies database not found');
return;
}
// site, use domain for query db
// e.g if site = space.bilibili.com, should include cookies under .bilibili.com
// postprocess after db query
let tlDomain;
if (site) {
tlDomain = psl_1.default.get(site) || undefined;
debug('site = %s, tlDomain = %s', site, tlDomain);
}
const allRows = await (0, db_1.query)(cookieDatabasePath, tlDomain);
const decryptor = new decrypt_1.ChromeCookieDecryptor(config.browserDir, config.keyringName, keyring);
await decryptor.init();
let decryptedRows = allRows.map((row) => {
const newrow = {
...(0, lodash_1.omit)(row, ['encryptedValue']),
// decrypt
value: decryptor.decrypt(row.encryptedValue) || row.value,
// expiresUtc = 0, means session cookie
expiresUtc: row.expiresUtc ? processChromeTimestamp(row.expiresUtc) : row.expiresUtc,
};
return newrow;
});
if (site && tlDomain) {
const allowedHostKey = [];
const parts = site.split('.');
let startIndex = 0;
const cur = () => parts.slice(startIndex).join('.');
while (cur().length >= tlDomain.length) {
allowedHostKey.push('.' + cur());
startIndex++;
}
debug('site = %s, allowedHostKey = %s', site, allowedHostKey);
decryptedRows = decryptedRows.filter((row) => {
return allowedHostKey.includes(row.hostKey);
});
}
return decryptedRows;
}
exports.readChromium = readChromium;
function isPath(value) {
return value?.includes(path_1.default.sep);
}
function findMostRecentlyUsedFile(root, filename) {
const files = globby_1.default.sync(`./**/${filename}`, {
cwd: root,
absolute: true,
});
const withStat = files.map((f) => ({ filepath: f, stat: fs_1.default.statSync(f) }));
const sorted = withStat.sort((a, b) => -(a.stat.mtimeMs - b.stat.mtimeMs)); // desc
const first = sorted[0]?.filepath;
return first;
}
/**
* def _get_chromium_based_browser_settings(browser_name):
*/
function getChromiumBasedBrowserSettings(browserName) {
// https://chromium.googlesource.com/chromium/src/+/HEAD/docs/user_data_dir.md
let browserDir = '';
(0, helper_1.execCrossPlatform)({
mac() {
const appdata = path_1.default.posix.join(os_1.default.homedir(), 'Library/Application Support');
const join = path_1.default.posix.join;
browserDir = {
brave: join(appdata, 'BraveSoftware/Brave-Browser'),
chrome: join(appdata, 'Google/Chrome'),
chromium: join(appdata, 'Chromium'),
edge: join(appdata, 'Microsoft Edge'),
opera: join(appdata, 'com.operasoftware.Opera'),
vivaldi: join(appdata, 'Vivaldi'),
}[browserName];
},
win() {
const appdata_local = process.env.LOCALAPPDATA;
const appdata_roaming = process.env.APPDATA;
if (!appdata_local || !appdata_roaming) {
throw new Error('expect LOCALAPPDATA & APPDATA environment variable');
}
const join = path_1.default.win32.join;
browserDir = {
brave: join(appdata_local, 'BraveSoftware/Brave-Browser/User Data'),
chrome: join(appdata_local, 'Google/Chrome/User Data'),
chromium: join(appdata_local, 'Chromium/User Data'),
edge: join(appdata_local, 'Microsoft/Edge/User Data'),
opera: join(appdata_roaming, 'Opera Software/Opera Stable'),
vivaldi: join(appdata_local, 'Vivaldi/User Data'),
}[browserName];
},
linux() {
const config = xdgConfigHome();
const join = path_1.default.posix.join;
browserDir = {
brave: join(config, 'BraveSoftware/Brave-Browser'),
chrome: join(config, 'google-chrome'),
chromium: join(config, 'chromium'),
edge: join(config, 'microsoft-edge'),
opera: join(config, 'opera'),
vivaldi: join(config, 'vivaldi'),
}[browserName];
},
});
const keyringName = {
brave: 'Brave',
chrome: 'Chrome',
chromium: 'Chromium',
edge: process.platform == 'darwin' ? 'Microsoft Edge' : 'Chromium',
opera: process.platform == 'darwin' ? 'Opera' : 'Chromium',
vivaldi: process.platform == 'darwin' ? 'Vivaldi' : 'Chrome',
}[browserName];
const browsersWithoutProfiles = ['opera'];
return {
browserDir: browserDir,
keyringName: keyringName,
supportsProfiles: !browsersWithoutProfiles.includes(browserName),
};
}
function xdgConfigHome() {
return process.env.XDG_CONFIG_HOME || path_1.default.join(os_1.default.homedir(), '.config');
}
/**
* 17 位时间戳的处理
* https://stackoverflow.com/questions/20458406/what-is-the-format-of-chromes-timestamps
* 11644473600 = 1970-1-1 - 1601-1-1, 单位 (s)
*/
function processChromeTimestamp(chromeTs) {
const unixTsInSeconds = chromeTs / 1000000 - 11644473600; // (s) len=10
const unixTsInMillSeconds = Math.round(unixTsInSeconds * 1000); // (ms) len=13
return unixTsInMillSeconds;
}
exports.processChromeTimestamp = processChromeTimestamp;