chrome-tools 1.0.6 → 1.0.8

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.
@@ -1,92 +1 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { resolveTargetProfiles } from "../core/chrome.js";
4
- import { chromeTimeToDate } from "../core/crypto.js";
5
- /**
6
- * Converts a Chrome bookmark timestamp (microseconds since 1601-01-01) to a date string.
7
- * Bookmark timestamps use the same epoch as Chrome timestamps but are stored as strings.
8
- */
9
- function bookmarkTimeToDate(timestamp) {
10
- if (!timestamp || timestamp === "0")
11
- return "N/A";
12
- return chromeTimeToDate(BigInt(timestamp));
13
- }
14
- /**
15
- * Recursively flattens the bookmark tree into a flat array.
16
- *
17
- * @param node - Bookmark tree node.
18
- * @param folderPath - Current folder path for context.
19
- * @returns Array of flattened bookmark entries.
20
- */
21
- function flattenBookmarks(node, folderPath = "") {
22
- const results = [];
23
- if (!node)
24
- return results;
25
- const currentPath = folderPath
26
- ? `${folderPath}/${node.name || ""}`
27
- : node.name || "";
28
- if (node.type === "url") {
29
- results.push({
30
- title: node.name || "",
31
- url: node.url || "",
32
- folder: folderPath,
33
- dateAdded: bookmarkTimeToDate(node.date_added),
34
- dateLastUsed: bookmarkTimeToDate(node.date_last_used),
35
- id: node.id,
36
- guid: node.guid || "",
37
- });
38
- }
39
- if (node.children && Array.isArray(node.children)) {
40
- for (const child of node.children) {
41
- results.push(...flattenBookmarks(child, currentPath));
42
- }
43
- }
44
- return results;
45
- }
46
- /**
47
- * Reads and parses bookmarks from Chrome's Bookmarks JSON file.
48
- *
49
- * @param options - Options for filtering and targeting.
50
- * @returns Filtered bookmark entries.
51
- */
52
- export function getBookmarks({ chromeDir, profile, folder, search } = {}) {
53
- const profiles = resolveTargetProfiles({ chromeDir, profile });
54
- const results = [];
55
- for (const p of profiles) {
56
- const bookmarksPath = join(p.path, "Bookmarks");
57
- if (!existsSync(bookmarksPath))
58
- continue;
59
- try {
60
- const raw = readFileSync(bookmarksPath, "utf-8");
61
- const data = JSON.parse(raw);
62
- const roots = data.roots;
63
- if (!roots)
64
- continue;
65
- // Flatten all root bookmark folders
66
- const rootNames = ["bookmark_bar", "other", "synced"];
67
- for (const rootName of rootNames) {
68
- if (roots[rootName]) {
69
- const bookmarks = flattenBookmarks(roots[rootName], "");
70
- for (const bm of bookmarks) {
71
- bm.profile = p.name;
72
- results.push(bm);
73
- }
74
- }
75
- }
76
- }
77
- catch {
78
- // Skip profiles with parse errors
79
- }
80
- }
81
- let filtered = results;
82
- if (folder) {
83
- const term = folder.toLowerCase();
84
- filtered = filtered.filter((b) => b.folder.toLowerCase().includes(term));
85
- }
86
- if (search) {
87
- const term = search.toLowerCase();
88
- filtered = filtered.filter((b) => b.title.toLowerCase().includes(term) ||
89
- b.url.toLowerCase().includes(term));
90
- }
91
- return filtered;
92
- }
1
+ import{existsSync as o,readFileSync as r}from"node:fs";import{join as e}from"node:path";import{resolveTargetProfiles as t}from"../core/chrome.js";import{chromeTimeToDate as n}from"../core/crypto.js";function s(o){return o&&"0"!==o?n(BigInt(o)):"N/A"}function i(o,r=""){const e=[];if(!o)return e;const t=r?`${r}/${o.name||""}`:o.name||"";if("url"===o.type&&e.push({title:o.name||"",url:o.url||"",folder:r,dateAdded:s(o.date_added),dateLastUsed:s(o.date_last_used),id:o.id,guid:o.guid||""}),o.children&&Array.isArray(o.children))for(const r of o.children)e.push(...i(r,t));return e}export function getBookmarks({chromeDir:n,profile:s,folder:c,search:f}={}){const d=t({chromeDir:n,profile:s}),a=[];for(const t of d){const n=e(t.path,"Bookmarks");if(o(n))try{const o=r(n,"utf-8"),e=JSON.parse(o).roots;if(!e)continue;const s=["bookmark_bar","other","synced"];for(const o of s)if(e[o]){const r=i(e[o],"");for(const o of r)o.profile=t.name,a.push(o)}}catch{}}let l=a;if(c){const o=c.toLowerCase();l=l.filter(r=>r.folder.toLowerCase().includes(o))}if(f){const o=f.toLowerCase();l=l.filter(r=>r.title.toLowerCase().includes(o)||r.url.toLowerCase().includes(o))}return l}
@@ -1,159 +1 @@
1
- import { join } from "node:path";
2
- import { createHash } from "node:crypto";
3
- import { resolveTargetProfiles } from "../core/chrome.js";
4
- import { deriveEncryptionKey } from "../core/keychain.js";
5
- import { decryptValueBuffer, chromeTimeToDate } from "../core/crypto.js";
6
- import { queryDatabase, tableExists } from "../core/db.js";
7
- function decodeCookieValue(encryptedValue, derivedKey, hostKey) {
8
- const decrypted = decryptValueBuffer(encryptedValue, derivedKey);
9
- if (!Buffer.isBuffer(decrypted)) {
10
- return decrypted;
11
- }
12
- const hostHash = createHash("sha256").update(hostKey).digest();
13
- const valueBytes = decrypted.length >= hostHash.length && decrypted.subarray(0, hostHash.length).equals(hostHash)
14
- ? decrypted.subarray(hostHash.length)
15
- : decrypted;
16
- return valueBytes.toString("utf-8");
17
- }
18
- function parseURL(urlStr) {
19
- if (!urlStr)
20
- return null;
21
- try {
22
- return new URL(urlStr);
23
- }
24
- catch {
25
- try {
26
- // If it doesn't have a protocol, prepend https://
27
- if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(urlStr)) {
28
- return new URL("https://" + urlStr);
29
- }
30
- }
31
- catch {
32
- // Ignore
33
- }
34
- }
35
- return null;
36
- }
37
- function getPossibleHostKeys(hostname) {
38
- const keys = new Set();
39
- keys.add(hostname);
40
- keys.add("." + hostname);
41
- const parts = hostname.split(".");
42
- for (let i = 1; i < parts.length; i++) {
43
- const suffix = parts.slice(i).join(".");
44
- keys.add(suffix);
45
- keys.add("." + suffix);
46
- }
47
- return Array.from(keys);
48
- }
49
- function domainMatch(hostname, hostKey) {
50
- if (hostname === hostKey) {
51
- return true;
52
- }
53
- if (hostKey.startsWith(".")) {
54
- if (hostname === hostKey.slice(1) || hostname.endsWith(hostKey)) {
55
- return true;
56
- }
57
- }
58
- return false;
59
- }
60
- function pathMatch(urlPath, cookiePath) {
61
- const uPath = urlPath || "/";
62
- const cPath = cookiePath || "/";
63
- if (cPath === "/") {
64
- return true;
65
- }
66
- if (uPath === cPath) {
67
- return true;
68
- }
69
- if (uPath.startsWith(cPath)) {
70
- if (cPath.endsWith("/") || uPath[cPath.length] === "/") {
71
- return true;
72
- }
73
- }
74
- return false;
75
- }
76
- /**
77
- * Extracts cookies from Chrome's Cookies database.
78
- * Encrypted values are decrypted using the same mechanism as passwords.
79
- *
80
- * @param options - Targeting and filtering options.
81
- * @returns Array of cookie entries.
82
- */
83
- export function getCookies({ chromeDir, profile, domain, url, decrypt = true, limit = 100, } = {}) {
84
- const profiles = resolveTargetProfiles({ chromeDir, profile });
85
- const derivedKey = decrypt ? deriveEncryptionKey() : null;
86
- const results = [];
87
- const parsedUrl = parseURL(url);
88
- for (const p of profiles) {
89
- const dbPath = join(p.path, "Cookies");
90
- try {
91
- if (!tableExists(dbPath, "cookies"))
92
- continue;
93
- let sql = `SELECT host_key, name, value, encrypted_value, path,
94
- expires_utc, is_secure, is_httponly, samesite,
95
- creation_utc, last_access_utc
96
- FROM cookies`;
97
- const conditions = [];
98
- const params = [];
99
- if (domain) {
100
- conditions.push(`host_key LIKE ?`);
101
- params.push(`%${domain}%`);
102
- }
103
- else if (parsedUrl) {
104
- const hostKeys = getPossibleHostKeys(parsedUrl.hostname);
105
- const placeholders = hostKeys.map(() => "?").join(", ");
106
- conditions.push(`host_key IN (${placeholders})`);
107
- params.push(...hostKeys);
108
- }
109
- if (conditions.length > 0) {
110
- sql += ` WHERE ${conditions.join(" AND ")}`;
111
- }
112
- sql += ` ORDER BY host_key, name`;
113
- if (limit && !url) {
114
- sql += ` LIMIT ${parseInt(limit, 10)}`;
115
- }
116
- const rows = queryDatabase(dbPath, sql, params);
117
- for (const row of rows) {
118
- if (parsedUrl) {
119
- if (!domainMatch(parsedUrl.hostname, row.host_key || ""))
120
- continue;
121
- if (!pathMatch(parsedUrl.pathname || "/", row.path || "/"))
122
- continue;
123
- if (row.is_secure && parsedUrl.protocol !== "https:")
124
- continue;
125
- }
126
- let cookieValue = row.value || "";
127
- if (!cookieValue && row.encrypted_value && derivedKey) {
128
- cookieValue = decodeCookieValue(row.encrypted_value, derivedKey, row.host_key || "");
129
- }
130
- results.push({
131
- profile: p.name,
132
- domain: row.host_key || "",
133
- name: row.name || "",
134
- value: cookieValue,
135
- path: row.path || "/",
136
- expires: chromeTimeToDate(row.expires_utc || 0),
137
- secure: Boolean(row.is_secure),
138
- httpOnly: Boolean(row.is_httponly),
139
- sameSite: ["unspecified", "lax", "strict", "none"][row.samesite] || "unspecified",
140
- created: chromeTimeToDate(row.creation_utc || 0),
141
- lastAccessed: chromeTimeToDate(row.last_access_utc || 0),
142
- });
143
- }
144
- }
145
- catch {
146
- // Skip profiles with errors
147
- }
148
- }
149
- if (limit && url) {
150
- return results.slice(0, parseInt(limit, 10));
151
- }
152
- return results;
153
- }
154
- export const _test = {
155
- parseURL,
156
- getPossibleHostKeys,
157
- domainMatch,
158
- pathMatch,
159
- };
1
+ import{join as t}from"node:path";import{createHash as e}from"node:crypto";import{resolveTargetProfiles as n}from"../core/chrome.js";import{deriveEncryptionKey as o}from"../core/keychain.js";import{decryptValueBuffer as s,chromeTimeToDate as r}from"../core/crypto.js";import{queryDatabase as c,tableExists as i}from"../core/db.js";function a(t,n,o){const r=s(t,n);if(!Buffer.isBuffer(r))return r;const c=e("sha256").update(o).digest();return(r.length>=c.length&&r.subarray(0,c.length).equals(c)?r.subarray(c.length):r).toString("utf-8")}function u(t){if(!t)return null;try{return new URL(t)}catch{try{if(!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(t))return new URL("https://"+t)}catch{}}return null}function h(t){const e=new Set;e.add(t),e.add("."+t);const n=t.split(".");for(let t=1;t<n.length;t++){const o=n.slice(t).join(".");e.add(o),e.add("."+o)}return Array.from(e)}function p(t,e){return t===e||!(!e.startsWith(".")||t!==e.slice(1)&&!t.endsWith(e))}function l(t,e){const n=t||"/",o=e||"/";return"/"===o||(n===o||!(!n.startsWith(o)||!o.endsWith("/")&&"/"!==n[o.length]))}export function getCookies({chromeDir:e,profile:s,domain:f,url:m,decrypt:d=!0,limit:y=100}={}){const _=n({chromeDir:e,profile:s}),k=d?o():null,g=[],j=u(m);for(const e of _){const n=t(e.path,"Cookies");try{if(!i(n,"cookies"))continue;let t="SELECT host_key, name, value, encrypted_value, path,\n expires_utc, is_secure, is_httponly, samesite,\n creation_utc, last_access_utc\n FROM cookies";const o=[],s=[];if(f)o.push("host_key LIKE ?"),s.push(`%${f}%`);else if(j){const t=h(j.hostname),e=t.map(()=>"?").join(", ");o.push(`host_key IN (${e})`),s.push(...t)}o.length>0&&(t+=` WHERE ${o.join(" AND ")}`),t+=" ORDER BY host_key, name",y&&!m&&(t+=` LIMIT ${parseInt(y,10)}`);const u=c(n,t,s);for(const t of u){if(j){if(!p(j.hostname,t.host_key||""))continue;if(!l(j.pathname||"/",t.path||"/"))continue;if(t.is_secure&&"https:"!==j.protocol)continue}let n=t.value||"";!n&&t.encrypted_value&&k&&(n=a(t.encrypted_value,k,t.host_key||"")),g.push({profile:e.name,domain:t.host_key||"",name:t.name||"",value:n,path:t.path||"/",expires:r(t.expires_utc||0),secure:Boolean(t.is_secure),httpOnly:Boolean(t.is_httponly),sameSite:["unspecified","lax","strict","none"][t.samesite]||"unspecified",created:r(t.creation_utc||0),lastAccessed:r(t.last_access_utc||0)})}}catch{}}return y&&m?g.slice(0,parseInt(y,10)):g}export const _test={parseURL:u,getPossibleHostKeys:h,domainMatch:p,pathMatch:l};
@@ -1,78 +1 @@
1
- import { join } from "node:path";
2
- import { resolveTargetProfiles } from "../core/chrome.js";
3
- import { chromeTimeToDate } from "../core/crypto.js";
4
- import { queryDatabase, tableExists } from "../core/db.js";
5
- /** Maps Chrome download state integers to human-readable strings. */
6
- const DOWNLOAD_STATES = {
7
- 0: "in_progress",
8
- 1: "complete",
9
- 2: "cancelled",
10
- 3: "interrupted",
11
- };
12
- /** Maps Chrome download danger type integers to strings. */
13
- const DANGER_TYPES = {
14
- 0: "not_dangerous",
15
- 1: "dangerous_file",
16
- 2: "dangerous_url",
17
- 3: "dangerous_content",
18
- 4: "maybe_dangerous_content",
19
- 5: "uncommon_content",
20
- 6: "user_validated",
21
- 7: "dangerous_host",
22
- 8: "potentially_unwanted",
23
- 9: "allowlisted_by_policy",
24
- };
25
- /**
26
- * Retrieves download history from Chrome's History database.
27
- *
28
- * @param options - Targeting, filtering, and limit options.
29
- * @returns Array of download entries.
30
- */
31
- export function getDownloads({ chromeDir, profile, search, limit = 100, } = {}) {
32
- const profiles = resolveTargetProfiles({ chromeDir, profile });
33
- const results = [];
34
- for (const p of profiles) {
35
- const dbPath = join(p.path, "History");
36
- try {
37
- if (!tableExists(dbPath, "downloads"))
38
- continue;
39
- let sql = `SELECT id, target_path, tab_url, total_bytes, received_bytes,
40
- start_time, end_time, mime_type, original_mime_type,
41
- state, danger_type, opened, referrer, site_url
42
- FROM downloads ORDER BY start_time DESC`;
43
- if (limit) {
44
- sql += ` LIMIT ${parseInt(limit, 10)}`;
45
- }
46
- const rows = queryDatabase(dbPath, sql);
47
- for (const row of rows) {
48
- const entry = {
49
- profile: p.name,
50
- targetPath: row.target_path || "",
51
- url: row.tab_url || "",
52
- siteUrl: row.site_url || "",
53
- referrer: row.referrer || "",
54
- totalBytes: row.total_bytes || 0,
55
- receivedBytes: row.received_bytes || 0,
56
- mimeType: row.mime_type || row.original_mime_type || "",
57
- state: DOWNLOAD_STATES[row.state] || `unknown(${row.state})`,
58
- dangerType: DANGER_TYPES[row.danger_type] || `unknown(${row.danger_type})`,
59
- opened: Boolean(row.opened),
60
- startTime: chromeTimeToDate(row.start_time || 0),
61
- endTime: chromeTimeToDate(row.end_time || 0),
62
- };
63
- if (search) {
64
- const term = search.toLowerCase();
65
- if (!(entry.targetPath && entry.targetPath.toLowerCase().includes(term)) &&
66
- !(entry.url && entry.url.toLowerCase().includes(term))) {
67
- continue;
68
- }
69
- }
70
- results.push(entry);
71
- }
72
- }
73
- catch {
74
- // Skip profiles with errors
75
- }
76
- }
77
- return results;
78
- }
1
+ import{join as e}from"node:path";import{resolveTargetProfiles as t}from"../core/chrome.js";import{chromeTimeToDate as r}from"../core/crypto.js";import{queryDatabase as o,tableExists as n}from"../core/db.js";const a={0:"in_progress",1:"complete",2:"cancelled",3:"interrupted"},s={0:"not_dangerous",1:"dangerous_file",2:"dangerous_url",3:"dangerous_content",4:"maybe_dangerous_content",5:"uncommon_content",6:"user_validated",7:"dangerous_host",8:"potentially_unwanted",9:"allowlisted_by_policy"};export function getDownloads({chromeDir:i,profile:_,search:l,limit:d=100}={}){const c=t({chromeDir:i,profile:_}),m=[];for(const t of c){const i=e(t.path,"History");try{if(!n(i,"downloads"))continue;let e="SELECT id, target_path, tab_url, total_bytes, received_bytes,\n start_time, end_time, mime_type, original_mime_type,\n state, danger_type, opened, referrer, site_url\n FROM downloads ORDER BY start_time DESC";d&&(e+=` LIMIT ${parseInt(d,10)}`);const _=o(i,e);for(const e of _){const o={profile:t.name,targetPath:e.target_path||"",url:e.tab_url||"",siteUrl:e.site_url||"",referrer:e.referrer||"",totalBytes:e.total_bytes||0,receivedBytes:e.received_bytes||0,mimeType:e.mime_type||e.original_mime_type||"",state:a[e.state]||`unknown(${e.state})`,dangerType:s[e.danger_type]||`unknown(${e.danger_type})`,opened:Boolean(e.opened),startTime:r(e.start_time||0),endTime:r(e.end_time||0)};if(l){const e=l.toLowerCase();if(!(o.targetPath&&o.targetPath.toLowerCase().includes(e)||o.url&&o.url.toLowerCase().includes(e)))continue}m.push(o)}}catch{}}return m}
@@ -1,70 +1 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { resolveTargetProfiles } from "../core/chrome.js";
4
- /**
5
- * Retrieves installed Chrome extensions from the Preferences JSON file.
6
- *
7
- * @param options - Targeting and filtering options.
8
- * @returns Array of extension entries.
9
- */
10
- export function getExtensions({ chromeDir, profile, search } = {}) {
11
- const profiles = resolveTargetProfiles({ chromeDir, profile });
12
- const results = [];
13
- for (const p of profiles) {
14
- // Try Secure Preferences first, fallback to Preferences
15
- let prefs = null;
16
- for (const filename of ["Secure Preferences", "Preferences"]) {
17
- const prefsPath = join(p.path, filename);
18
- if (existsSync(prefsPath)) {
19
- try {
20
- prefs = JSON.parse(readFileSync(prefsPath, "utf-8"));
21
- break;
22
- }
23
- catch {
24
- continue;
25
- }
26
- }
27
- }
28
- if (!prefs)
29
- continue;
30
- const settings = prefs?.extensions?.settings;
31
- if (!settings || typeof settings !== "object")
32
- continue;
33
- for (const [extId, extDataAny] of Object.entries(settings)) {
34
- const extData = extDataAny;
35
- // Skip component extensions and internal Chrome extensions
36
- if (extData.location === 5 || extData.location === 10)
37
- continue;
38
- const manifest = extData.manifest || {};
39
- const entry = {
40
- profile: p.name,
41
- id: extId,
42
- name: manifest.name || extData.name || extId,
43
- version: manifest.version || extData.version || "unknown",
44
- description: manifest.description || "",
45
- enabled: extData.state === 1,
46
- homepageUrl: manifest.homepage_url || "",
47
- updateUrl: manifest.update_url || "",
48
- installTime: extData.install_time || "",
49
- fromWebstore: extData.from_webstore || false,
50
- location: extData.location || 0,
51
- };
52
- // Clean up __MSG_ placeholders in name/description
53
- if (entry.name.startsWith("__MSG_")) {
54
- entry.name = extId; // Fallback to ID if name is a message key
55
- }
56
- if (entry.description.startsWith("__MSG_")) {
57
- entry.description = "";
58
- }
59
- if (search) {
60
- const term = search.toLowerCase();
61
- if (!(entry.name && entry.name.toLowerCase().includes(term)) &&
62
- !(entry.id && entry.id.toLowerCase().includes(term))) {
63
- continue;
64
- }
65
- }
66
- results.push(entry);
67
- }
68
- }
69
- return results;
70
- }
1
+ import{existsSync as e,readFileSync as o}from"node:fs";import{join as t}from"node:path";import{resolveTargetProfiles as n}from"../core/chrome.js";export function getExtensions({chromeDir:r,profile:i,search:s}={}){const c=n({chromeDir:r,profile:i}),a=[];for(const n of c){let r=null;for(const i of["Secure Preferences","Preferences"]){const s=t(n.path,i);if(e(s))try{r=JSON.parse(o(s,"utf-8"));break}catch{continue}}if(!r)continue;const i=r?.extensions?.settings;if(i&&"object"==typeof i)for(const[e,o]of Object.entries(i)){const t=o;if(5===t.location||10===t.location)continue;const r=t.manifest||{},i={profile:n.name,id:e,name:r.name||t.name||e,version:r.version||t.version||"unknown",description:r.description||"",enabled:1===t.state,homepageUrl:r.homepage_url||"",updateUrl:r.update_url||"",installTime:t.install_time||"",fromWebstore:t.from_webstore||!1,location:t.location||0};if(i.name.startsWith("__MSG_")&&(i.name=e),i.description.startsWith("__MSG_")&&(i.description=""),s){const e=s.toLowerCase();if(!(i.name&&i.name.toLowerCase().includes(e)||i.id&&i.id.toLowerCase().includes(e)))continue}a.push(i)}}return a}
@@ -1,84 +1 @@
1
- import { join } from "node:path";
2
- import { resolveTargetProfiles } from "../core/chrome.js";
3
- import { chromeTimeToDate } from "../core/crypto.js";
4
- import { queryDatabase, tableExists } from "../core/db.js";
5
- /**
6
- * Retrieves browsing history from Chrome's History database.
7
- *
8
- * @param options - Filtering and targeting options.
9
- * @returns Array of history entries.
10
- */
11
- export function getHistory({ chromeDir, profile, search, limit = 100 } = {}) {
12
- const profiles = resolveTargetProfiles({ chromeDir, profile });
13
- const results = [];
14
- for (const p of profiles) {
15
- const dbPath = join(p.path, "History");
16
- try {
17
- if (!tableExists(dbPath, "urls"))
18
- continue;
19
- let sql = `SELECT url, title, visit_count, last_visit_time
20
- FROM urls ORDER BY last_visit_time DESC`;
21
- if (limit) {
22
- sql += ` LIMIT ${parseInt(limit, 10)}`;
23
- }
24
- const rows = queryDatabase(dbPath, sql);
25
- for (const row of rows) {
26
- const entry = {
27
- profile: p.name,
28
- url: row.url,
29
- title: row.title || "",
30
- visitCount: row.visit_count || 0,
31
- lastVisit: chromeTimeToDate(row.last_visit_time || 0),
32
- };
33
- if (search) {
34
- const term = search.toLowerCase();
35
- if (!(entry.url && entry.url.toLowerCase().includes(term)) &&
36
- !(entry.title && entry.title.toLowerCase().includes(term))) {
37
- continue;
38
- }
39
- }
40
- results.push(entry);
41
- }
42
- }
43
- catch {
44
- // Skip profiles with errors
45
- }
46
- }
47
- return results;
48
- }
49
- /**
50
- * Retrieves search terms from Chrome's History database.
51
- *
52
- * @param options - Targeting and limit options.
53
- * @returns Array of search term entries.
54
- */
55
- export function getSearchTerms({ chromeDir, profile, limit = 100 } = {}) {
56
- const profiles = resolveTargetProfiles({ chromeDir, profile });
57
- const results = [];
58
- for (const p of profiles) {
59
- const dbPath = join(p.path, "History");
60
- try {
61
- if (!tableExists(dbPath, "keyword_search_terms"))
62
- continue;
63
- let sql = `SELECT kst.term, u.url
64
- FROM keyword_search_terms kst
65
- JOIN urls u ON kst.url_id = u.id
66
- ORDER BY u.last_visit_time DESC`;
67
- if (limit) {
68
- sql += ` LIMIT ${parseInt(limit, 10)}`;
69
- }
70
- const rows = queryDatabase(dbPath, sql);
71
- for (const row of rows) {
72
- results.push({
73
- profile: p.name,
74
- term: row.term || "",
75
- url: row.url || "",
76
- });
77
- }
78
- }
79
- catch {
80
- // Skip profiles with errors
81
- }
82
- }
83
- return results;
84
- }
1
+ import{join as t}from"node:path";import{resolveTargetProfiles as r}from"../core/chrome.js";import{chromeTimeToDate as o}from"../core/crypto.js";import{queryDatabase as e,tableExists as i}from"../core/db.js";export function getHistory({chromeDir:s,profile:c,search:n,limit:l=100}={}){const u=r({chromeDir:s,profile:c}),m=[];for(const r of u){const s=t(r.path,"History");try{if(!i(s,"urls"))continue;let t="SELECT url, title, visit_count, last_visit_time\n FROM urls ORDER BY last_visit_time DESC";l&&(t+=` LIMIT ${parseInt(l,10)}`);const c=e(s,t);for(const t of c){const e={profile:r.name,url:t.url,title:t.title||"",visitCount:t.visit_count||0,lastVisit:o(t.last_visit_time||0)};if(n){const t=n.toLowerCase();if(!(e.url&&e.url.toLowerCase().includes(t)||e.title&&e.title.toLowerCase().includes(t)))continue}m.push(e)}}catch{}}return m}export function getSearchTerms({chromeDir:o,profile:s,limit:c=100}={}){const n=r({chromeDir:o,profile:s}),l=[];for(const r of n){const o=t(r.path,"History");try{if(!i(o,"keyword_search_terms"))continue;let t="SELECT kst.term, u.url\n FROM keyword_search_terms kst\n JOIN urls u ON kst.url_id = u.id\n ORDER BY u.last_visit_time DESC";c&&(t+=` LIMIT ${parseInt(c,10)}`);const s=e(o,t);for(const t of s)l.push({profile:r.name,term:t.term||"",url:t.url||""})}catch{}}return l}
@@ -1,50 +1 @@
1
- import { join } from "node:path";
2
- import { resolveTargetProfiles } from "../core/chrome.js";
3
- import { deriveEncryptionKey } from "../core/keychain.js";
4
- import { decryptValue, chromeTimeToDate } from "../core/crypto.js";
5
- import { queryDatabase, tableExists } from "../core/db.js";
6
- /**
7
- * Extracts and decrypts saved passwords from Chrome's Login Data database.
8
- *
9
- * @param options - Options for profile selection and filtering.
10
- * @returns Array of decrypted password entries.
11
- */
12
- export function getPasswords({ chromeDir, profile, search } = {}) {
13
- const profiles = resolveTargetProfiles({ chromeDir, profile });
14
- const derivedKey = deriveEncryptionKey();
15
- const results = [];
16
- for (const p of profiles) {
17
- const dbPath = join(p.path, "Login Data");
18
- try {
19
- if (!tableExists(dbPath, "logins"))
20
- continue;
21
- const rows = queryDatabase(dbPath, `SELECT origin_url, username_value, password_value, date_last_used
22
- FROM logins ORDER BY origin_url`);
23
- for (const row of rows) {
24
- const entry = {
25
- profile: p.name,
26
- url: row.origin_url,
27
- username: row.username_value,
28
- password: decryptValue(row.password_value, derivedKey),
29
- lastUsed: chromeTimeToDate(row.date_last_used || 0),
30
- };
31
- if (search) {
32
- const term = search.toLowerCase();
33
- if (!(entry.url && entry.url.toLowerCase().includes(term)) &&
34
- !(entry.username && entry.username.toLowerCase().includes(term))) {
35
- continue;
36
- }
37
- }
38
- results.push(entry);
39
- }
40
- }
41
- catch (err) {
42
- // Skip profiles with errors, continue with others
43
- results.push({
44
- profile: p.name,
45
- error: err.message,
46
- });
47
- }
48
- }
49
- return results.filter((r) => !r.error);
50
- }
1
+ import{join as r}from"node:path";import{resolveTargetProfiles as o}from"../core/chrome.js";import{deriveEncryptionKey as e}from"../core/keychain.js";import{decryptValue as s,chromeTimeToDate as t}from"../core/crypto.js";import{queryDatabase as n,tableExists as a}from"../core/db.js";export function getPasswords({chromeDir:i,profile:u,search:c}={}){const l=o({chromeDir:i,profile:u}),m=e(),f=[];for(const o of l){const e=r(o.path,"Login Data");try{if(!a(e,"logins"))continue;const r=n(e,"SELECT origin_url, username_value, password_value, date_last_used\n FROM logins ORDER BY origin_url");for(const e of r){const r={profile:o.name,url:e.origin_url,username:e.username_value,password:s(e.password_value,m),lastUsed:t(e.date_last_used||0)};if(c){const o=c.toLowerCase();if(!(r.url&&r.url.toLowerCase().includes(o)||r.username&&r.username.toLowerCase().includes(o)))continue}f.push(r)}}catch(r){f.push({profile:o.name,error:r.message})}}return f.filter(r=>!r.error)}
@@ -1,40 +1 @@
1
- import { join } from "node:path";
2
- import { resolveTargetProfiles } from "../core/chrome.js";
3
- import { queryDatabase, tableExists } from "../core/db.js";
4
- /**
5
- * Retrieves search engines configured in Chrome from the Web Data database.
6
- *
7
- * @param options - Targeting options.
8
- * @returns Array of search engine entries.
9
- */
10
- export function getSearchEngines({ chromeDir, profile } = {}) {
11
- const profiles = resolveTargetProfiles({ chromeDir, profile });
12
- const results = [];
13
- for (const p of profiles) {
14
- const dbPath = join(p.path, "Web Data");
15
- try {
16
- if (!tableExists(dbPath, "keywords"))
17
- continue;
18
- const rows = queryDatabase(dbPath, `SELECT id, short_name, keyword, url, suggestions_url,
19
- favicon_url, prepopulate_id, is_active
20
- FROM keywords ORDER BY short_name`);
21
- for (const row of rows) {
22
- results.push({
23
- profile: p.name,
24
- id: row.id,
25
- name: row.short_name || "",
26
- keyword: row.keyword || "",
27
- url: row.url || "",
28
- suggestionsUrl: row.suggestions_url || "",
29
- faviconUrl: row.favicon_url || "",
30
- isDefault: row.prepopulate_id === 1,
31
- isActive: row.is_active === 1,
32
- });
33
- }
34
- }
35
- catch {
36
- // Skip profiles with errors
37
- }
38
- }
39
- return results;
40
- }
1
+ import{join as o}from"node:path";import{resolveTargetProfiles as r}from"../core/chrome.js";import{queryDatabase as e,tableExists as i}from"../core/db.js";export function getSearchEngines({chromeDir:t,profile:s}={}){const n=r({chromeDir:t,profile:s}),c=[];for(const r of n){const t=o(r.path,"Web Data");try{if(!i(t,"keywords"))continue;const o=e(t,"SELECT id, short_name, keyword, url, suggestions_url,\n favicon_url, prepopulate_id, is_active\n FROM keywords ORDER BY short_name");for(const e of o)c.push({profile:r.name,id:e.id,name:e.short_name||"",keyword:e.keyword||"",url:e.url||"",suggestionsUrl:e.suggestions_url||"",faviconUrl:e.favicon_url||"",isDefault:1===e.prepopulate_id,isActive:1===e.is_active})}catch{}}return c}
@@ -1,35 +1 @@
1
- import { join } from "node:path";
2
- import { resolveTargetProfiles } from "../core/chrome.js";
3
- import { queryDatabase, tableExists } from "../core/db.js";
4
- /**
5
- * Retrieves top sites from Chrome's Top Sites database.
6
- *
7
- * @param options - Targeting options.
8
- * @returns Array of top site entries.
9
- */
10
- export function getTopSites({ chromeDir, profile } = {}) {
11
- const profiles = resolveTargetProfiles({ chromeDir, profile });
12
- const results = [];
13
- for (const p of profiles) {
14
- const dbPath = join(p.path, "Top Sites");
15
- try {
16
- if (!tableExists(dbPath, "top_sites"))
17
- continue;
18
- const rows = queryDatabase(dbPath, `SELECT url, url_rank, title, redirects
19
- FROM top_sites ORDER BY url_rank ASC`);
20
- for (const row of rows) {
21
- results.push({
22
- profile: p.name,
23
- url: row.url || "",
24
- title: row.title || "",
25
- rank: row.url_rank ?? 0,
26
- redirects: row.redirects || "",
27
- });
28
- }
29
- }
30
- catch {
31
- // Skip profiles with errors
32
- }
33
- }
34
- return results;
35
- }
1
+ import{join as r}from"node:path";import{resolveTargetProfiles as t}from"../core/chrome.js";import{queryDatabase as o,tableExists as e}from"../core/db.js";export function getTopSites({chromeDir:i,profile:c}={}){const n=t({chromeDir:i,profile:c}),s=[];for(const t of n){const i=r(t.path,"Top Sites");try{if(!e(i,"top_sites"))continue;const r=o(i,"SELECT url, url_rank, title, redirects\n FROM top_sites ORDER BY url_rank ASC");for(const o of r)s.push({profile:t.name,url:o.url||"",title:o.title||"",rank:o.url_rank??0,redirects:o.redirects||""})}catch{}}return s}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "chrome-tools",
3
- "version": "1.0.6",
4
- "description": "CLI & Node.js library to extract Chrome browser data (bookmarks, history, cookies, autofill, extensions, and more) on macOS",
3
+ "version": "1.0.8",
4
+ "description": "CLI & Node.js library for chrome",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
7
7
  "exports": {
@@ -26,7 +26,7 @@
26
26
  "LICENSE"
27
27
  ],
28
28
  "scripts": {
29
- "build": "tsc",
29
+ "build": "node scripts/clean.mjs && tsc && node scripts/minify.mjs",
30
30
  "start": "node dist/bin/cli.js",
31
31
  "test": "npm run build && node --test dist/test/*.test.js dist/test/**/*.test.js && node dist/bin/cli.js --help",
32
32
  "release": "npm run build && npm version patch --no-git-tag-version && npm publish"
@@ -56,6 +56,7 @@
56
56
  "devDependencies": {
57
57
  "@types/better-sqlite3": "^7.6.13",
58
58
  "@types/node": "^25.9.1",
59
+ "terser": "^5.44.0",
59
60
  "typescript": "^6.0.3"
60
61
  }
61
62
  }