@maskzh/chrome-cookies-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browsers.d.ts +10 -0
- package/dist/browsers.js +53 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +135 -0
- package/dist/db.d.ts +13 -0
- package/dist/db.js +45 -0
- package/dist/decrypt.d.ts +4 -0
- package/dist/decrypt.js +36 -0
- package/dist/filter.d.ts +14 -0
- package/dist/filter.js +30 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +56 -0
- package/dist/profiles.d.ts +6 -0
- package/dist/profiles.js +37 -0
- package/package.json +50 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface BrowserConfig {
|
|
2
|
+
basePath: string;
|
|
3
|
+
keychainAccount: string;
|
|
4
|
+
keychainService: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getBrowser(name: string): BrowserConfig;
|
|
7
|
+
export declare function listBrowsers(): {
|
|
8
|
+
name: string;
|
|
9
|
+
basePath: string;
|
|
10
|
+
}[];
|
package/dist/browsers.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
const HOME = os.homedir();
|
|
4
|
+
const BROWSER_REGISTRY = {
|
|
5
|
+
chrome: {
|
|
6
|
+
basePath: `${HOME}/Library/Application Support/Google/Chrome`,
|
|
7
|
+
keychainAccount: "Chrome",
|
|
8
|
+
keychainService: "Chrome Safe Storage",
|
|
9
|
+
},
|
|
10
|
+
chromium: {
|
|
11
|
+
basePath: `${HOME}/Library/Application Support/Chromium`,
|
|
12
|
+
keychainAccount: "Chromium",
|
|
13
|
+
keychainService: "Chromium Safe Storage",
|
|
14
|
+
},
|
|
15
|
+
arc: {
|
|
16
|
+
basePath: `${HOME}/Library/Application Support/Arc/User Data`,
|
|
17
|
+
keychainAccount: "Arc",
|
|
18
|
+
keychainService: "Arc Safe Storage",
|
|
19
|
+
},
|
|
20
|
+
edge: {
|
|
21
|
+
basePath: `${HOME}/Library/Application Support/Microsoft Edge`,
|
|
22
|
+
keychainAccount: "Microsoft Edge",
|
|
23
|
+
keychainService: "Microsoft Edge Safe Storage",
|
|
24
|
+
},
|
|
25
|
+
brave: {
|
|
26
|
+
basePath: `${HOME}/Library/Application Support/BraveSoftware/Brave-Browser`,
|
|
27
|
+
keychainAccount: "Brave",
|
|
28
|
+
keychainService: "Brave Safe Storage",
|
|
29
|
+
},
|
|
30
|
+
vivaldi: {
|
|
31
|
+
basePath: `${HOME}/Library/Application Support/Vivaldi`,
|
|
32
|
+
keychainAccount: "Vivaldi",
|
|
33
|
+
keychainService: "Vivaldi Safe Storage",
|
|
34
|
+
},
|
|
35
|
+
opera: {
|
|
36
|
+
basePath: `${HOME}/Library/Application Support/com.operasoftware.Opera`,
|
|
37
|
+
keychainAccount: "Opera",
|
|
38
|
+
keychainService: "Opera Safe Storage",
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
export function getBrowser(name) {
|
|
42
|
+
const config = BROWSER_REGISTRY[name.toLowerCase()];
|
|
43
|
+
if (!config) {
|
|
44
|
+
const available = Object.keys(BROWSER_REGISTRY).join(", ");
|
|
45
|
+
throw new Error(`Unknown browser: "${name}". Available: ${available}`);
|
|
46
|
+
}
|
|
47
|
+
return config;
|
|
48
|
+
}
|
|
49
|
+
export function listBrowsers() {
|
|
50
|
+
return Object.entries(BROWSER_REGISTRY)
|
|
51
|
+
.filter(([, config]) => existsSync(config.basePath))
|
|
52
|
+
.map(([name, config]) => ({ name, basePath: config.basePath }));
|
|
53
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { getCookies, listBrowsers, listProfiles } from "./index.js";
|
|
3
|
+
import { getBrowser } from "./browsers.js";
|
|
4
|
+
const HELP = `
|
|
5
|
+
Usage:
|
|
6
|
+
bun src/cli.ts <url> [options]
|
|
7
|
+
bun src/cli.ts --list-profiles [--browser <name>]
|
|
8
|
+
bun src/cli.ts --list-browsers
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--browser, -b <name> Browser name (default: chrome)
|
|
12
|
+
--profile, -p <name> Profile name or directory name (default: Default)
|
|
13
|
+
--filter, -f <names> Comma-separated cookie name prefixes to include
|
|
14
|
+
--json Output as JSON
|
|
15
|
+
--list-profiles List available profiles for the specified browser
|
|
16
|
+
--list-browsers List browsers installed on this system
|
|
17
|
+
--help, -h Show this help
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
bun src/cli.ts https://example.com
|
|
21
|
+
bun src/cli.ts https://example.com -b chrome -p Youzan
|
|
22
|
+
bun src/cli.ts https://example.com -b chrome -p Youzan -f cas,xiaolv
|
|
23
|
+
bun src/cli.ts https://example.com --json
|
|
24
|
+
bun src/cli.ts --list-profiles --browser chrome
|
|
25
|
+
`.trim();
|
|
26
|
+
function parseArgs(argv) {
|
|
27
|
+
const args = argv.slice(2);
|
|
28
|
+
const result = {
|
|
29
|
+
url: "",
|
|
30
|
+
browser: "chrome",
|
|
31
|
+
browserSet: false,
|
|
32
|
+
profile: "Default",
|
|
33
|
+
filter: undefined,
|
|
34
|
+
json: false,
|
|
35
|
+
listProfiles: false,
|
|
36
|
+
listBrowsers: false,
|
|
37
|
+
help: false,
|
|
38
|
+
};
|
|
39
|
+
for (let i = 0; i < args.length; i++) {
|
|
40
|
+
const arg = args[i];
|
|
41
|
+
switch (arg) {
|
|
42
|
+
case "--browser":
|
|
43
|
+
case "-b":
|
|
44
|
+
result.browser = args[++i];
|
|
45
|
+
result.browserSet = true;
|
|
46
|
+
break;
|
|
47
|
+
case "--profile":
|
|
48
|
+
case "-p":
|
|
49
|
+
result.profile = args[++i];
|
|
50
|
+
break;
|
|
51
|
+
case "--filter":
|
|
52
|
+
case "-f":
|
|
53
|
+
result.filter = args[++i];
|
|
54
|
+
break;
|
|
55
|
+
case "--json":
|
|
56
|
+
result.json = true;
|
|
57
|
+
break;
|
|
58
|
+
case "--list-profiles":
|
|
59
|
+
result.listProfiles = true;
|
|
60
|
+
break;
|
|
61
|
+
case "--list-browsers":
|
|
62
|
+
result.listBrowsers = true;
|
|
63
|
+
break;
|
|
64
|
+
case "--help":
|
|
65
|
+
case "-h":
|
|
66
|
+
result.help = true;
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
if (!arg.startsWith("-"))
|
|
70
|
+
result.url = arg;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
async function main() {
|
|
76
|
+
const args = parseArgs(process.argv);
|
|
77
|
+
if (args.help) {
|
|
78
|
+
console.log(HELP);
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
if (args.listBrowsers) {
|
|
82
|
+
const browsers = listBrowsers();
|
|
83
|
+
if (args.json) {
|
|
84
|
+
console.log(JSON.stringify(browsers, null, 2));
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log("Installed browsers:");
|
|
88
|
+
browsers.forEach(({ name, basePath }) => console.log(` ${name.padEnd(10)} ${basePath}`));
|
|
89
|
+
}
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
if (args.listProfiles) {
|
|
93
|
+
const browsers = args.browserSet
|
|
94
|
+
? [{ name: args.browser, basePath: getBrowser(args.browser).basePath }]
|
|
95
|
+
: listBrowsers();
|
|
96
|
+
if (args.json) {
|
|
97
|
+
const result = Object.fromEntries(browsers.map(({ name, basePath }) => [name, listProfiles(basePath)]));
|
|
98
|
+
console.log(JSON.stringify(result, null, 2));
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
for (const { name, basePath } of browsers) {
|
|
102
|
+
console.log(`Profiles for ${name}:`);
|
|
103
|
+
listProfiles(basePath).forEach(({ dir, name }) => console.log(` ${dir.padEnd(12)} ${name}`));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
if (!args.url) {
|
|
109
|
+
console.error("Error: URL is required.\n");
|
|
110
|
+
console.log(HELP);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
const filterPrefixes = args.filter?.split(",").map((s) => s.trim().toLowerCase());
|
|
114
|
+
const cookieFilter = filterPrefixes
|
|
115
|
+
? ({ name }) => {
|
|
116
|
+
const lower = name.toLowerCase();
|
|
117
|
+
return filterPrefixes.some((prefix) => lower === prefix || lower.startsWith(prefix));
|
|
118
|
+
}
|
|
119
|
+
: undefined;
|
|
120
|
+
const result = await getCookies(args.url, {
|
|
121
|
+
browser: args.browser,
|
|
122
|
+
profile: args.profile,
|
|
123
|
+
filter: cookieFilter,
|
|
124
|
+
});
|
|
125
|
+
if (args.json) {
|
|
126
|
+
console.log(JSON.stringify(result, null, 2));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.log(result.cookieString);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
main().catch((err) => {
|
|
133
|
+
console.error("Error:", err.message);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
});
|
package/dist/db.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface RawCookieRow {
|
|
2
|
+
hostKey: string;
|
|
3
|
+
path: string;
|
|
4
|
+
isSecure: number;
|
|
5
|
+
name: string;
|
|
6
|
+
encryptedValue: Buffer;
|
|
7
|
+
creationUtc: number;
|
|
8
|
+
expiresUtc: number;
|
|
9
|
+
isHttponly: number;
|
|
10
|
+
hasExpires: number;
|
|
11
|
+
isPersistent: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function readRawCookies(cookiesDbPath: string, domain: string): Promise<RawCookieRow[]>;
|
package/dist/db.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { copyFileSync, readFileSync, unlinkSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import initSqlJs from "sql.js";
|
|
5
|
+
export async function readRawCookies(cookiesDbPath, domain) {
|
|
6
|
+
const tmpPath = join(os.tmpdir(), `chromium-cookies-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
7
|
+
try {
|
|
8
|
+
copyFileSync(cookiesDbPath, tmpPath);
|
|
9
|
+
const SQL = await initSqlJs();
|
|
10
|
+
const db = new SQL.Database(readFileSync(tmpPath));
|
|
11
|
+
const stmt = db.prepare(`SELECT host_key, path, is_secure, name, encrypted_value, creation_utc, expires_utc, is_httponly, has_expires, is_persistent
|
|
12
|
+
FROM cookies
|
|
13
|
+
WHERE host_key LIKE :domain
|
|
14
|
+
ORDER BY LENGTH(path) DESC, creation_utc ASC`);
|
|
15
|
+
const rows = [];
|
|
16
|
+
stmt.bind({ ":domain": `%${domain}` });
|
|
17
|
+
while (stmt.step()) {
|
|
18
|
+
const row = stmt.get();
|
|
19
|
+
const [host_key, path, is_secure, name, encrypted_value, creation_utc, expires_utc, is_httponly, has_expires, is_persistent,] = row;
|
|
20
|
+
rows.push({
|
|
21
|
+
hostKey: host_key,
|
|
22
|
+
path,
|
|
23
|
+
isSecure: is_secure,
|
|
24
|
+
name,
|
|
25
|
+
encryptedValue: Buffer.from(encrypted_value.buffer, encrypted_value.byteOffset, encrypted_value.byteLength),
|
|
26
|
+
creationUtc: creation_utc,
|
|
27
|
+
expiresUtc: expires_utc,
|
|
28
|
+
isHttponly: is_httponly,
|
|
29
|
+
hasExpires: has_expires,
|
|
30
|
+
isPersistent: is_persistent,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
stmt.free();
|
|
34
|
+
db.close();
|
|
35
|
+
return rows;
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
try {
|
|
39
|
+
unlinkSync(tmpPath);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* 临时文件可能已不存在 */
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
export declare function getKeychainPassword(account: string, service: string): Promise<string>;
|
|
3
|
+
export declare function deriveKey(password: string): Promise<Buffer>;
|
|
4
|
+
export declare function decryptCookieValue(key: crypto.CipherKey, encryptedValue: Buffer): string;
|
package/dist/decrypt.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import keychain from "keychain";
|
|
3
|
+
const ITERATIONS = 1003;
|
|
4
|
+
const KEYLENGTH = 16;
|
|
5
|
+
const SALT = "saltysalt";
|
|
6
|
+
export function getKeychainPassword(account, service) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
keychain.getPassword({ account, service }, (err, password) => {
|
|
9
|
+
if (err)
|
|
10
|
+
return reject(err);
|
|
11
|
+
resolve(password);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export function deriveKey(password) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
crypto.pbkdf2(password, SALT, ITERATIONS, KEYLENGTH, "sha1", (err, key) => {
|
|
18
|
+
if (err)
|
|
19
|
+
return reject(err);
|
|
20
|
+
resolve(key);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function decryptCookieValue(key, encryptedValue) {
|
|
25
|
+
// Chromium 在 macOS 上加密的 cookie 前缀为 "v10"(3 字节),需跳过
|
|
26
|
+
const iv = Buffer.alloc(KEYLENGTH, " ");
|
|
27
|
+
const decipher = crypto.createDecipheriv("aes-128-cbc", key, iv);
|
|
28
|
+
decipher.setAutoPadding(false);
|
|
29
|
+
const data = encryptedValue.slice(3);
|
|
30
|
+
let decoded = Buffer.concat([decipher.update(data), decipher.final()]);
|
|
31
|
+
const padding = decoded[decoded.length - 1];
|
|
32
|
+
if (padding) {
|
|
33
|
+
decoded = decoded.slice(32, decoded.length - padding);
|
|
34
|
+
}
|
|
35
|
+
return decoded.toString("utf8");
|
|
36
|
+
}
|
package/dist/filter.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import url from "url";
|
|
2
|
+
export interface Cookie {
|
|
3
|
+
name: string;
|
|
4
|
+
value: string;
|
|
5
|
+
hostKey: string;
|
|
6
|
+
path: string;
|
|
7
|
+
isSecure: number;
|
|
8
|
+
isHttponly: number;
|
|
9
|
+
isPersistent: number;
|
|
10
|
+
hasExpires: number;
|
|
11
|
+
creationUtc: number;
|
|
12
|
+
expiresUtc: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function filterCookies(parsedUrl: url.UrlWithStringQuery, cookies: Cookie[]): Cookie[];
|
package/dist/filter.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import tough from "tough-cookie";
|
|
2
|
+
export function filterCookies(parsedUrl, cookies) {
|
|
3
|
+
const path = parsedUrl.path ?? "/";
|
|
4
|
+
const hostname = parsedUrl.hostname;
|
|
5
|
+
const isSecure = parsedUrl.protocol === "https:";
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
const filtered = [];
|
|
8
|
+
const matched = cookies.filter((cookie) => {
|
|
9
|
+
if (cookie.isSecure && !isSecure)
|
|
10
|
+
return false;
|
|
11
|
+
if (!tough.domainMatch(hostname, cookie.hostKey, true))
|
|
12
|
+
return false;
|
|
13
|
+
if (!tough.pathMatch(path, cookie.path))
|
|
14
|
+
return false;
|
|
15
|
+
// Chromium 的 expiresUtc 使用 Windows FILETIME 纪元(1601-01-01),
|
|
16
|
+
// 与 Unix 时间(1970-01-01)相差 11644473600 秒
|
|
17
|
+
if (cookie.expiresUtc) {
|
|
18
|
+
return cookie.expiresUtc / 1000 - 11644473600000 > Date.now();
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
});
|
|
22
|
+
for (let i = matched.length - 1; i >= 0; i--) {
|
|
23
|
+
const cookie = matched[i];
|
|
24
|
+
if (!seen.has(cookie.name)) {
|
|
25
|
+
seen.add(cookie.name);
|
|
26
|
+
filtered.push(cookie);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return filtered.reverse();
|
|
30
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { listBrowsers } from "./browsers.js";
|
|
2
|
+
export { listProfiles } from "./profiles.js";
|
|
3
|
+
export interface GetCookiesOptions {
|
|
4
|
+
browser?: string;
|
|
5
|
+
profile?: string;
|
|
6
|
+
filter?: (cookie: {
|
|
7
|
+
name: string;
|
|
8
|
+
value: string;
|
|
9
|
+
}) => boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface GetCookiesResult {
|
|
12
|
+
cookieString: string;
|
|
13
|
+
cookies: {
|
|
14
|
+
name: string;
|
|
15
|
+
value: string;
|
|
16
|
+
}[];
|
|
17
|
+
}
|
|
18
|
+
export declare function getCookies(uri: string, options?: GetCookiesOptions): Promise<GetCookiesResult>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import tld from "tldjs";
|
|
4
|
+
import { Cookie, CookieJar } from "tough-cookie";
|
|
5
|
+
import url from "url";
|
|
6
|
+
import { getBrowser } from "./browsers.js";
|
|
7
|
+
import { readRawCookies } from "./db.js";
|
|
8
|
+
import { decryptCookieValue, deriveKey, getKeychainPassword } from "./decrypt.js";
|
|
9
|
+
import { filterCookies } from "./filter.js";
|
|
10
|
+
import { resolveProfile } from "./profiles.js";
|
|
11
|
+
export { listBrowsers } from "./browsers.js";
|
|
12
|
+
export { listProfiles } from "./profiles.js";
|
|
13
|
+
export async function getCookies(uri, options = {}) {
|
|
14
|
+
const { browser = "chrome", profile = "Default", filter } = options;
|
|
15
|
+
const parsedUrl = url.parse(uri);
|
|
16
|
+
assert(parsedUrl.protocol && parsedUrl.hostname, 'Could not parse URI, format should be "https://www.example.com/path/"');
|
|
17
|
+
const domain = tld.getDomain(uri);
|
|
18
|
+
assert(domain, `Could not extract domain from URI: ${uri}`);
|
|
19
|
+
const browserConfig = getBrowser(browser);
|
|
20
|
+
const profileDir = resolveProfile(browserConfig.basePath, profile);
|
|
21
|
+
const cookiesPath = join(browserConfig.basePath, profileDir, "Cookies");
|
|
22
|
+
const [derivedKey, rawRows] = await Promise.all([
|
|
23
|
+
getKeychainPassword(browserConfig.keychainAccount, browserConfig.keychainService).then(deriveKey),
|
|
24
|
+
readRawCookies(cookiesPath, domain),
|
|
25
|
+
]);
|
|
26
|
+
const cookies = rawRows.map((row) => {
|
|
27
|
+
let value = "";
|
|
28
|
+
try {
|
|
29
|
+
value = decryptCookieValue(derivedKey, row.encryptedValue);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// 解密失败时跳过,返回空值
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
name: row.name,
|
|
36
|
+
value,
|
|
37
|
+
hostKey: row.hostKey,
|
|
38
|
+
path: row.path,
|
|
39
|
+
isSecure: row.isSecure,
|
|
40
|
+
isHttponly: row.isHttponly,
|
|
41
|
+
isPersistent: row.isPersistent,
|
|
42
|
+
hasExpires: row.hasExpires,
|
|
43
|
+
creationUtc: row.creationUtc,
|
|
44
|
+
expiresUtc: row.expiresUtc,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
const filteredByUser = filter ? cookies.filter(filter) : cookies;
|
|
48
|
+
const finalCookies = filterCookies(parsedUrl, filteredByUser);
|
|
49
|
+
const jar = new CookieJar();
|
|
50
|
+
await Promise.all(finalCookies.map(({ name, value }) => jar.setCookie(new Cookie({ key: name, value }), uri)));
|
|
51
|
+
const cookieString = await jar.getCookieString(uri);
|
|
52
|
+
return {
|
|
53
|
+
cookieString,
|
|
54
|
+
cookies: finalCookies.map(({ name, value }) => ({ name, value })),
|
|
55
|
+
};
|
|
56
|
+
}
|
package/dist/profiles.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
const DIR_NAME_RE = /^(Default|Profile \d+)$/;
|
|
4
|
+
function loadInfoCache(basePath) {
|
|
5
|
+
try {
|
|
6
|
+
const content = readFileSync(join(basePath, "Local State"), "utf8");
|
|
7
|
+
return JSON.parse(content)?.profile?.info_cache ?? null;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function resolveProfile(basePath, nameOrDir) {
|
|
14
|
+
if (!nameOrDir)
|
|
15
|
+
return "Default";
|
|
16
|
+
if (DIR_NAME_RE.test(nameOrDir))
|
|
17
|
+
return nameOrDir;
|
|
18
|
+
const infoCache = loadInfoCache(basePath);
|
|
19
|
+
if (!infoCache) {
|
|
20
|
+
throw new Error(`Cannot resolve profile name "${nameOrDir}": browser has no "Local State" file at ${basePath}. ` +
|
|
21
|
+
`Please use the directory name directly (e.g. "Default", "Profile 1").`);
|
|
22
|
+
}
|
|
23
|
+
const entry = Object.entries(infoCache).find(([, info]) => info.name.toLowerCase() === nameOrDir.toLowerCase());
|
|
24
|
+
if (!entry) {
|
|
25
|
+
const available = Object.entries(infoCache)
|
|
26
|
+
.map(([dir, info]) => ` ${dir}: "${info.name}"`)
|
|
27
|
+
.join("\n");
|
|
28
|
+
throw new Error(`Profile "${nameOrDir}" not found. Available profiles:\n${available}`);
|
|
29
|
+
}
|
|
30
|
+
return entry[0];
|
|
31
|
+
}
|
|
32
|
+
export function listProfiles(basePath) {
|
|
33
|
+
const infoCache = loadInfoCache(basePath);
|
|
34
|
+
if (!infoCache)
|
|
35
|
+
return [{ dir: "Default", name: "Default" }];
|
|
36
|
+
return Object.entries(infoCache).map(([dir, info]) => ({ dir, name: info.name }));
|
|
37
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@maskzh/chrome-cookies-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Standalone Chromium cookie reader with profile name resolution",
|
|
5
|
+
"bin": {
|
|
6
|
+
"chrome-cookies": "./dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"prepublishOnly": "npm run build",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"lint": "oxlint src",
|
|
28
|
+
"lint:fix": "oxlint src --fix",
|
|
29
|
+
"format": "oxfmt src",
|
|
30
|
+
"format:check": "oxfmt --check src",
|
|
31
|
+
"list-browsers": "bun src/cli.ts --list-browsers",
|
|
32
|
+
"list-profiles": "bun src/cli.ts --list-profiles"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"keychain": "^1.4.0",
|
|
36
|
+
"sql.js": "^1.10.0",
|
|
37
|
+
"tldjs": "^2.3.1",
|
|
38
|
+
"tough-cookie": "^4.1.4"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/keychain": "^1.4.4",
|
|
42
|
+
"@types/node": "^20.0.0",
|
|
43
|
+
"@types/sql.js": "^1.4.9",
|
|
44
|
+
"@types/tldjs": "^2.3.4",
|
|
45
|
+
"@types/tough-cookie": "^4.0.5",
|
|
46
|
+
"oxfmt": "^0.42.0",
|
|
47
|
+
"oxlint": "^1.57.0",
|
|
48
|
+
"typescript": "^5.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|