chrome-tools 1.1.5 → 1.1.7

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.
Files changed (45) hide show
  1. package/dist/bin/cli.js +556 -1
  2. package/dist/src/core/browsers.d.ts +7 -3
  3. package/dist/src/core/browsers.js +186 -1
  4. package/dist/src/core/chrome.d.ts +6 -4
  5. package/dist/src/core/chrome.js +240 -1
  6. package/dist/src/core/crypto.d.ts +1 -1
  7. package/dist/src/core/crypto.js +113 -1
  8. package/dist/src/core/db.d.ts +4 -2
  9. package/dist/src/core/db.js +90 -1
  10. package/dist/src/core/keychain.d.ts +10 -0
  11. package/dist/src/core/keychain.js +191 -1
  12. package/dist/src/core/limit.d.ts +4 -0
  13. package/dist/src/core/limit.js +12 -0
  14. package/dist/src/core/platform/darwin.js +31 -1
  15. package/dist/src/core/platform/index.d.ts +4 -3
  16. package/dist/src/core/platform/index.js +69 -1
  17. package/dist/src/core/platform/linux.js +64 -1
  18. package/dist/src/core/platform/types.d.ts +3 -2
  19. package/dist/src/core/platform/types.js +1 -1
  20. package/dist/src/core/platform/win32.js +114 -1
  21. package/dist/src/core/sql.d.ts +2 -0
  22. package/dist/src/core/sql.js +4 -0
  23. package/dist/src/index.d.ts +2 -2
  24. package/dist/src/index.js +34 -1
  25. package/dist/src/modules/autofill.d.ts +8 -15
  26. package/dist/src/modules/autofill.js +170 -1
  27. package/dist/src/modules/bookmarks.d.ts +3 -5
  28. package/dist/src/modules/bookmarks.js +83 -1
  29. package/dist/src/modules/cookies.d.ts +4 -5
  30. package/dist/src/modules/cookies.js +157 -1
  31. package/dist/src/modules/downloads.d.ts +3 -5
  32. package/dist/src/modules/downloads.js +80 -1
  33. package/dist/src/modules/extensions.d.ts +3 -5
  34. package/dist/src/modules/extensions.js +71 -1
  35. package/dist/src/modules/history.d.ts +5 -10
  36. package/dist/src/modules/history.js +83 -1
  37. package/dist/src/modules/local-storage.d.ts +3 -5
  38. package/dist/src/modules/local-storage.js +455 -1
  39. package/dist/src/modules/passwords.d.ts +4 -5
  40. package/dist/src/modules/passwords.js +64 -1
  41. package/dist/src/modules/search-engines.d.ts +3 -5
  42. package/dist/src/modules/search-engines.js +38 -1
  43. package/dist/src/modules/top-sites.d.ts +3 -5
  44. package/dist/src/modules/top-sites.js +33 -1
  45. package/package.json +14 -12
@@ -1 +1,157 @@
1
- import{join as t}from"node:path";import{createHash as e}from"node:crypto";import{resolveTargetProfiles as o}from"../core/chrome.js";import{deriveEncryptionKey as n,decryptValueBuffer as s}from"../core/platform/index.js";import{chromeTimeToDate as r}from"../core/crypto.js";import{queryDatabase as i,tableExists as c}from"../core/db.js";function a(t,o,n){const r=s(t,o);if(!Buffer.isBuffer(r))return r;const i=e("sha256").update(n).digest();return(r.length>=i.length&&r.subarray(0,i.length).equals(i)?r.subarray(i.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 p(t){const e=new Set;e.add(t),e.add("."+t);const o=t.split(".");for(let t=1;t<o.length;t++){const n=o.slice(t).join(".");e.add(n),e.add("."+n)}return Array.from(e)}function h(t,e){return t===e||!(!e.startsWith(".")||t!==e.slice(1)&&!t.endsWith(e))}function l(t,e){const o=t||"/",n=e||"/";return"/"===n||(o===n||!(!o.startsWith(n)||!n.endsWith("/")&&"/"!==o[n.length]))}export function getCookies({chromeDir:e,profile:s,browser:f,domain:m,url:d,decrypt:_=!0,limit:y=100}={}){const g=o({chromeDir:e,profile:s,browser:f}),k=_?n():null,j=[],x=u(d);for(const e of g){const o=t(e.path,"Cookies");try{if(!c(o,"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 n=[],s=[];if(m)n.push("host_key LIKE ?"),s.push(`%${m}%`);else if(x){const t=p(x.hostname),e=t.map(()=>"?").join(", ");n.push(`host_key IN (${e})`),s.push(...t)}n.length>0&&(t+=` WHERE ${n.join(" AND ")}`),t+=" ORDER BY host_key, name",y&&!d&&(t+=` LIMIT ${parseInt(y,10)}`);const u=i(o,t,s);for(const t of u){if(x){if(!h(x.hostname,t.host_key||""))continue;if(!l(x.pathname||"/",t.path||"/"))continue;if(t.is_secure&&"https:"!==x.protocol)continue}let o=t.value||"";!o&&t.encrypted_value&&k&&(o=a(t.encrypted_value,k,t.host_key||"")),j.push({profile:e.name,domain:t.host_key||"",name:t.name||"",value:o,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&&d?j.slice(0,parseInt(y,10)):j}export const _test={parseURL:u,getPossibleHostKeys:p,domainMatch:h,pathMatch:l};
1
+ import { join } from "node:path";
2
+ import { createHash } from "node:crypto";
3
+ import { resolveTargetProfiles } from "../core/chrome.js";
4
+ import { deriveEncryptionKey, decryptValueBuffer } from "../core/platform/index.js";
5
+ import { chromeTimeToDate } from "../core/crypto.js";
6
+ import { queryDatabase } from "../core/db.js";
7
+ import { limitResults, normalizeLimit } from "../core/limit.js";
8
+ function decodeCookieValue(encryptedValue, derivedKey, hostKey) {
9
+ const decrypted = decryptValueBuffer(encryptedValue, derivedKey);
10
+ if (!Buffer.isBuffer(decrypted)) {
11
+ return decrypted;
12
+ }
13
+ const hostHash = createHash("sha256").update(hostKey).digest();
14
+ const valueBytes = decrypted.length >= hostHash.length && decrypted.subarray(0, hostHash.length).equals(hostHash)
15
+ ? decrypted.subarray(hostHash.length)
16
+ : decrypted;
17
+ return valueBytes.toString("utf-8");
18
+ }
19
+ function parseURL(urlStr) {
20
+ if (!urlStr)
21
+ return null;
22
+ try {
23
+ return new URL(urlStr);
24
+ }
25
+ catch {
26
+ try {
27
+ // If it doesn't have a protocol, prepend https://
28
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(urlStr)) {
29
+ return new URL("https://" + urlStr);
30
+ }
31
+ }
32
+ catch {
33
+ // Ignore
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+ function getPossibleHostKeys(hostname) {
39
+ const keys = new Set();
40
+ keys.add(hostname);
41
+ keys.add("." + hostname);
42
+ const parts = hostname.split(".");
43
+ for (let i = 1; i < parts.length; i++) {
44
+ const suffix = parts.slice(i).join(".");
45
+ keys.add(suffix);
46
+ keys.add("." + suffix);
47
+ }
48
+ return Array.from(keys);
49
+ }
50
+ function domainMatch(hostname, hostKey) {
51
+ if (hostname === hostKey) {
52
+ return true;
53
+ }
54
+ if (hostKey.startsWith(".")) {
55
+ if (hostname === hostKey.slice(1) || hostname.endsWith(hostKey)) {
56
+ return true;
57
+ }
58
+ }
59
+ return false;
60
+ }
61
+ function pathMatch(urlPath, cookiePath) {
62
+ const uPath = urlPath || "/";
63
+ const cPath = cookiePath || "/";
64
+ if (cPath === "/") {
65
+ return true;
66
+ }
67
+ if (uPath === cPath) {
68
+ return true;
69
+ }
70
+ if (uPath.startsWith(cPath)) {
71
+ if (cPath.endsWith("/") || uPath[cPath.length] === "/") {
72
+ return true;
73
+ }
74
+ }
75
+ return false;
76
+ }
77
+ /**
78
+ * Extracts cookies from Chrome's Cookies database.
79
+ * Encrypted values are decrypted using the same mechanism as passwords.
80
+ *
81
+ * @param options - Targeting and filtering options.
82
+ * @returns Array of cookie entries.
83
+ */
84
+ export function getCookies({ chromeDir, profilePath, profile, browser, domain, url, decrypt = true, limit = 100, decryptionKey, } = {}) {
85
+ const userDataDir = profilePath || chromeDir;
86
+ const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
87
+ let derivedKey = decryptionKey;
88
+ const results = [];
89
+ const parsedUrl = parseURL(url);
90
+ const rowLimit = normalizeLimit(limit);
91
+ for (const p of profiles) {
92
+ const dbPath = join(p.path, "Cookies");
93
+ try {
94
+ let sql = `SELECT host_key, name, value, encrypted_value, path,
95
+ expires_utc, is_secure, is_httponly, samesite,
96
+ creation_utc, last_access_utc
97
+ FROM cookies`;
98
+ const conditions = [];
99
+ const params = [];
100
+ if (domain) {
101
+ conditions.push(`host_key LIKE ?`);
102
+ params.push(`%${domain}%`);
103
+ }
104
+ else if (parsedUrl) {
105
+ const hostKeys = getPossibleHostKeys(parsedUrl.hostname);
106
+ const placeholders = hostKeys.map(() => "?").join(", ");
107
+ conditions.push(`host_key IN (${placeholders})`);
108
+ params.push(...hostKeys);
109
+ }
110
+ if (conditions.length > 0) {
111
+ sql += ` WHERE ${conditions.join(" AND ")}`;
112
+ }
113
+ sql += ` ORDER BY host_key, name`;
114
+ if (rowLimit && !url) {
115
+ sql += ` LIMIT ${rowLimit}`;
116
+ }
117
+ const rows = queryDatabase(dbPath, sql, params);
118
+ for (const row of rows) {
119
+ if (parsedUrl) {
120
+ if (!domainMatch(parsedUrl.hostname, row.host_key || ""))
121
+ continue;
122
+ if (!pathMatch(parsedUrl.pathname || "/", row.path || "/"))
123
+ continue;
124
+ if (row.is_secure && parsedUrl.protocol !== "https:")
125
+ continue;
126
+ }
127
+ let cookieValue = row.value || "";
128
+ if (!cookieValue && row.encrypted_value?.length > 0 && decrypt) {
129
+ cookieValue = decodeCookieValue(row.encrypted_value, derivedKey ??= deriveEncryptionKey(userDataDir, browser), row.host_key || "");
130
+ }
131
+ results.push({
132
+ profile: p.name,
133
+ domain: row.host_key || "",
134
+ name: row.name || "",
135
+ value: cookieValue,
136
+ path: row.path || "/",
137
+ expires: chromeTimeToDate(row.expires_utc || 0),
138
+ secure: Boolean(row.is_secure),
139
+ httpOnly: Boolean(row.is_httponly),
140
+ sameSite: ["unspecified", "lax", "strict", "none"][row.samesite] || "unspecified",
141
+ created: chromeTimeToDate(row.creation_utc || 0),
142
+ lastAccessed: chromeTimeToDate(row.last_access_utc || 0),
143
+ });
144
+ }
145
+ }
146
+ catch {
147
+ // Skip profiles with errors
148
+ }
149
+ }
150
+ return limitResults(results, limit);
151
+ }
152
+ export const _test = {
153
+ parseURL,
154
+ getPossibleHostKeys,
155
+ domainMatch,
156
+ pathMatch,
157
+ };
@@ -1,7 +1,5 @@
1
- export interface DownloadOptions {
2
- chromeDir?: string;
3
- profile?: string;
4
- browser?: string;
1
+ import { type ResolveProfilesOptions } from "../core/chrome.js";
2
+ export interface DownloadOptions extends ResolveProfilesOptions {
5
3
  search?: string;
6
4
  limit?: number;
7
5
  }
@@ -26,4 +24,4 @@ export interface DownloadEntry {
26
24
  * @param options - Targeting, filtering, and limit options.
27
25
  * @returns Array of download entries.
28
26
  */
29
- export declare function getDownloads({ chromeDir, profile, browser, search, limit, }?: DownloadOptions): DownloadEntry[];
27
+ export declare function getDownloads({ chromeDir, profilePath, profile, browser, search, limit, }?: DownloadOptions): DownloadEntry[];
@@ -1 +1,80 @@
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:_,browser:l,search:d,limit:c=100}={}){const m=t({chromeDir:i,profile:_,browser:l}),p=[];for(const t of m){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";c&&(e+=` LIMIT ${parseInt(c,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(d){const e=d.toLowerCase();if(!(o.targetPath&&o.targetPath.toLowerCase().includes(e)||o.url&&o.url.toLowerCase().includes(e)))continue}p.push(o)}}catch{}}return p}
1
+ import { join } from "node:path";
2
+ import { resolveTargetProfiles } from "../core/chrome.js";
3
+ import { chromeTimeToDate } from "../core/crypto.js";
4
+ import { queryDatabase } from "../core/db.js";
5
+ import { limitResults, normalizeLimit } from "../core/limit.js";
6
+ import { sqlLikePattern } from "../core/sql.js";
7
+ /** Maps Chrome download state integers to human-readable strings. */
8
+ const DOWNLOAD_STATES = {
9
+ 0: "in_progress",
10
+ 1: "complete",
11
+ 2: "cancelled",
12
+ 3: "interrupted",
13
+ };
14
+ /** Maps Chrome download danger type integers to strings. */
15
+ const DANGER_TYPES = {
16
+ 0: "not_dangerous",
17
+ 1: "dangerous_file",
18
+ 2: "dangerous_url",
19
+ 3: "dangerous_content",
20
+ 4: "maybe_dangerous_content",
21
+ 5: "uncommon_content",
22
+ 6: "user_validated",
23
+ 7: "dangerous_host",
24
+ 8: "potentially_unwanted",
25
+ 9: "allowlisted_by_policy",
26
+ };
27
+ /**
28
+ * Retrieves download history from Chrome's History database.
29
+ *
30
+ * @param options - Targeting, filtering, and limit options.
31
+ * @returns Array of download entries.
32
+ */
33
+ export function getDownloads({ chromeDir, profilePath, profile, browser, search, limit = 100, } = {}) {
34
+ const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
35
+ const results = [];
36
+ const rowLimit = normalizeLimit(limit);
37
+ for (const p of profiles) {
38
+ const dbPath = join(p.path, "History");
39
+ try {
40
+ // Apply search in SQL before LIMIT so older matches are not truncated away.
41
+ let sql = `SELECT target_path, tab_url, total_bytes, received_bytes,
42
+ start_time, end_time, mime_type, original_mime_type,
43
+ state, danger_type, opened, referrer, site_url
44
+ FROM downloads`;
45
+ const params = [];
46
+ if (search) {
47
+ sql += ` WHERE IFNULL(target_path, '') LIKE ? ESCAPE '\\'
48
+ OR IFNULL(tab_url, '') LIKE ? ESCAPE '\\'`;
49
+ const like = sqlLikePattern(search);
50
+ params.push(like, like);
51
+ }
52
+ sql += ` ORDER BY start_time DESC`;
53
+ if (rowLimit) {
54
+ sql += ` LIMIT ${rowLimit}`;
55
+ }
56
+ const rows = queryDatabase(dbPath, sql, params);
57
+ for (const row of rows) {
58
+ results.push({
59
+ profile: p.name,
60
+ targetPath: row.target_path || "",
61
+ url: row.tab_url || "",
62
+ siteUrl: row.site_url || "",
63
+ referrer: row.referrer || "",
64
+ totalBytes: row.total_bytes || 0,
65
+ receivedBytes: row.received_bytes || 0,
66
+ mimeType: row.mime_type || row.original_mime_type || "",
67
+ state: DOWNLOAD_STATES[row.state] || `unknown(${row.state})`,
68
+ dangerType: DANGER_TYPES[row.danger_type] || `unknown(${row.danger_type})`,
69
+ opened: Boolean(row.opened),
70
+ startTime: chromeTimeToDate(row.start_time || 0),
71
+ endTime: chromeTimeToDate(row.end_time || 0),
72
+ });
73
+ }
74
+ }
75
+ catch {
76
+ // Skip profiles with errors
77
+ }
78
+ }
79
+ return limitResults(results, limit);
80
+ }
@@ -1,7 +1,5 @@
1
- export interface ExtensionOptions {
2
- chromeDir?: string;
3
- profile?: string;
4
- browser?: string;
1
+ import { type ResolveProfilesOptions } from "../core/chrome.js";
2
+ export interface ExtensionOptions extends ResolveProfilesOptions {
5
3
  search?: string;
6
4
  }
7
5
  export interface ExtensionEntry {
@@ -23,4 +21,4 @@ export interface ExtensionEntry {
23
21
  * @param options - Targeting and filtering options.
24
22
  * @returns Array of extension entries.
25
23
  */
26
- export declare function getExtensions({ chromeDir, profile, search, browser }?: ExtensionOptions): ExtensionEntry[];
24
+ export declare function getExtensions({ chromeDir, profilePath, profile, search, browser, }?: ExtensionOptions): ExtensionEntry[];
@@ -1 +1,71 @@
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,browser:c}={}){const a=n({chromeDir:r,profile:i,browser:c}),f=[];for(const n of a){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}f.push(i)}}return f}
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, profilePath, profile, search, browser, } = {}) {
11
+ const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
12
+ const results = [];
13
+ for (const p of profiles) {
14
+ // Prefer Secure Preferences, but continue to Preferences if it has no settings.
15
+ let settings = null;
16
+ for (const filename of ["Secure Preferences", "Preferences"]) {
17
+ const prefsPath = join(p.path, filename);
18
+ if (existsSync(prefsPath)) {
19
+ try {
20
+ const prefs = JSON.parse(readFileSync(prefsPath, "utf-8"));
21
+ const candidate = prefs?.extensions?.settings;
22
+ if (candidate && typeof candidate === "object") {
23
+ settings = candidate;
24
+ break;
25
+ }
26
+ }
27
+ catch {
28
+ continue;
29
+ }
30
+ }
31
+ }
32
+ if (!settings)
33
+ continue;
34
+ for (const [extId, extDataAny] of Object.entries(settings)) {
35
+ const extData = extDataAny;
36
+ // Skip component extensions and internal Chrome extensions
37
+ if (extData.location === 5 || extData.location === 10)
38
+ continue;
39
+ const manifest = extData.manifest || {};
40
+ const entry = {
41
+ profile: p.name,
42
+ id: extId,
43
+ name: manifest.name || extData.name || extId,
44
+ version: manifest.version || extData.version || "unknown",
45
+ description: manifest.description || "",
46
+ enabled: extData.state === 1,
47
+ homepageUrl: manifest.homepage_url || "",
48
+ updateUrl: manifest.update_url || "",
49
+ installTime: extData.install_time || "",
50
+ fromWebstore: extData.from_webstore || false,
51
+ location: extData.location || 0,
52
+ };
53
+ // Clean up __MSG_ placeholders in name/description
54
+ if (entry.name.startsWith("__MSG_")) {
55
+ entry.name = extId; // Fallback to ID if name is a message key
56
+ }
57
+ if (entry.description.startsWith("__MSG_")) {
58
+ entry.description = "";
59
+ }
60
+ if (search) {
61
+ const term = search.toLowerCase();
62
+ if (!(entry.name && entry.name.toLowerCase().includes(term)) &&
63
+ !(entry.id && entry.id.toLowerCase().includes(term))) {
64
+ continue;
65
+ }
66
+ }
67
+ results.push(entry);
68
+ }
69
+ }
70
+ return results;
71
+ }
@@ -1,7 +1,5 @@
1
- export interface HistoryOptions {
2
- chromeDir?: string;
3
- profile?: string;
4
- browser?: string;
1
+ import { type ResolveProfilesOptions } from "../core/chrome.js";
2
+ export interface HistoryOptions extends ResolveProfilesOptions {
5
3
  search?: string;
6
4
  limit?: number;
7
5
  }
@@ -12,10 +10,7 @@ export interface HistoryEntry {
12
10
  visitCount: number;
13
11
  lastVisit: string;
14
12
  }
15
- export interface SearchTermOptions {
16
- chromeDir?: string;
17
- profile?: string;
18
- browser?: string;
13
+ export interface SearchTermOptions extends ResolveProfilesOptions {
19
14
  limit?: number;
20
15
  }
21
16
  export interface SearchTermEntry {
@@ -29,11 +24,11 @@ export interface SearchTermEntry {
29
24
  * @param options - Filtering and targeting options.
30
25
  * @returns Array of history entries.
31
26
  */
32
- export declare function getHistory({ chromeDir, profile, search, limit, browser }?: HistoryOptions): HistoryEntry[];
27
+ export declare function getHistory({ chromeDir, profilePath, profile, search, limit, browser, }?: HistoryOptions): HistoryEntry[];
33
28
  /**
34
29
  * Retrieves search terms from Chrome's History database.
35
30
  *
36
31
  * @param options - Targeting and limit options.
37
32
  * @returns Array of search term entries.
38
33
  */
39
- export declare function getSearchTerms({ chromeDir, profile, limit, browser }?: SearchTermOptions): SearchTermEntry[];
34
+ export declare function getSearchTerms({ chromeDir, profilePath, profile, limit, browser, }?: SearchTermOptions): SearchTermEntry[];
@@ -1 +1,83 @@
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,browser:u}={}){const m=r({chromeDir:s,profile:c,browser:u}),f=[];for(const r of m){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}f.push(e)}}catch{}}return f}export function getSearchTerms({chromeDir:o,profile:s,limit:c=100,browser:n}={}){const l=r({chromeDir:o,profile:s,browser:n}),u=[];for(const r of l){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)u.push({profile:r.name,term:t.term||"",url:t.url||""})}catch{}}return u}
1
+ import { join } from "node:path";
2
+ import { resolveTargetProfiles } from "../core/chrome.js";
3
+ import { chromeTimeToDate } from "../core/crypto.js";
4
+ import { queryDatabase } from "../core/db.js";
5
+ import { limitResults, normalizeLimit } from "../core/limit.js";
6
+ import { sqlLikePattern } from "../core/sql.js";
7
+ /**
8
+ * Retrieves browsing history from Chrome's History database.
9
+ *
10
+ * @param options - Filtering and targeting options.
11
+ * @returns Array of history entries.
12
+ */
13
+ export function getHistory({ chromeDir, profilePath, profile, search, limit = 100, browser, } = {}) {
14
+ const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
15
+ const results = [];
16
+ const rowLimit = normalizeLimit(limit);
17
+ for (const p of profiles) {
18
+ const dbPath = join(p.path, "History");
19
+ try {
20
+ // Apply search in SQL before LIMIT so older matches are not truncated away.
21
+ let sql = `SELECT url, title, visit_count, last_visit_time FROM urls`;
22
+ const params = [];
23
+ if (search) {
24
+ sql += ` WHERE url LIKE ? ESCAPE '\\' OR IFNULL(title, '') LIKE ? ESCAPE '\\'`;
25
+ const like = sqlLikePattern(search);
26
+ params.push(like, like);
27
+ }
28
+ sql += ` ORDER BY last_visit_time DESC`;
29
+ if (rowLimit) {
30
+ sql += ` LIMIT ${rowLimit}`;
31
+ }
32
+ const rows = queryDatabase(dbPath, sql, params);
33
+ for (const row of rows) {
34
+ results.push({
35
+ profile: p.name,
36
+ url: row.url,
37
+ title: row.title || "",
38
+ visitCount: row.visit_count || 0,
39
+ lastVisit: chromeTimeToDate(row.last_visit_time || 0),
40
+ });
41
+ }
42
+ }
43
+ catch {
44
+ // Skip profiles with errors
45
+ }
46
+ }
47
+ return limitResults(results, limit);
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, profilePath, profile, limit = 100, browser, } = {}) {
56
+ const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
57
+ const results = [];
58
+ const rowLimit = normalizeLimit(limit);
59
+ for (const p of profiles) {
60
+ const dbPath = join(p.path, "History");
61
+ try {
62
+ let sql = `SELECT kst.term, u.url
63
+ FROM keyword_search_terms kst
64
+ JOIN urls u ON kst.url_id = u.id
65
+ ORDER BY u.last_visit_time DESC`;
66
+ if (rowLimit) {
67
+ sql += ` LIMIT ${rowLimit}`;
68
+ }
69
+ const rows = queryDatabase(dbPath, sql);
70
+ for (const row of rows) {
71
+ results.push({
72
+ profile: p.name,
73
+ term: row.term || "",
74
+ url: row.url || "",
75
+ });
76
+ }
77
+ }
78
+ catch {
79
+ // Skip profiles with errors
80
+ }
81
+ }
82
+ return limitResults(results, limit);
83
+ }
@@ -1,7 +1,5 @@
1
- export interface LocalStorageOptions {
2
- chromeDir?: string;
3
- profile?: string;
4
- browser?: string;
1
+ import { type ResolveProfilesOptions } from "../core/chrome.js";
2
+ export interface LocalStorageOptions extends ResolveProfilesOptions {
5
3
  origin?: string;
6
4
  url?: string;
7
5
  key?: string;
@@ -26,7 +24,7 @@ interface RawLevelDBRecord {
26
24
  * @param options - Targeting and filtering options.
27
25
  * @returns Array of localStorage entries.
28
26
  */
29
- export declare function getLocalStorage({ chromeDir, profile, browser, origin, url, key, search, limit, }?: LocalStorageOptions): LocalStorageEntry[];
27
+ export declare function getLocalStorage({ chromeDir, profilePath, profile, browser, origin, url, key, search, limit, }?: LocalStorageOptions): LocalStorageEntry[];
30
28
  declare function parseLocalStorageRecord(rawKey: Buffer, rawValue: Buffer): {
31
29
  origin: string;
32
30
  key: string;