-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.js
More file actions
124 lines (108 loc) · 3.92 KB
/
Copy pathsearch.js
File metadata and controls
124 lines (108 loc) · 3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#!/usr/bin/env node
/**
* search.js — Search bookmarks in default browser.
*
* Usage: bm <query>
*/
"use strict";
const fs = require("fs");
const path = require("path");
const { readInput, writeResponse, list, error, stripKeyword } = require("@yoki/plugin-sdk");
const { getDefaultBrowser, getChromiumProfiles, getFirefoxProfiles } = require("./browser");
// --- Bookmark parsing ---
function findChromiumBookmarks(profilePath) {
const bmFile = path.join(profilePath, "Bookmarks");
if (!fs.existsSync(bmFile)) return [];
try {
const data = JSON.parse(fs.readFileSync(bmFile, "utf-8"));
const results = [];
const walk = (node, folder) => {
if (!node) return;
if (node.type === "url") {
results.push({ name: node.name || "", url: node.url || "", folder: folder.join(" / ") });
} else if (node.type === "folder" && node.children) {
const path = [...folder, node.name || ""];
node.children.forEach(c => walk(c, path));
}
};
if (data.roots) Object.values(data.roots).forEach(r => walk(r, []));
return results;
} catch { return []; }
}
function findFirefoxBookmarks(profilePath) {
// Firefox bookmarks are in places.sqlite — need better-sqlite3
const Database = require("better-sqlite3");
const placesFile = path.join(profilePath, "places.sqlite");
if (!fs.existsSync(placesFile)) return [];
const os = require("os");
const tmp = path.join(os.tmpdir(), `yoki_ff_bm_${Date.now()}.db`);
try {
fs.copyFileSync(placesFile, tmp);
const db = new Database(tmp, { readonly: true, fileMustExist: true });
const rows = db.prepare(`
SELECT b.title, p.url, pa.title as folder
FROM moz_bookmarks b
JOIN moz_places p ON b.fk = p.id
LEFT JOIN moz_bookmarks pa ON b.parent = pa.id
WHERE b.type = 1 AND p.url NOT LIKE 'place:%'
`).all();
db.close();
return rows.map(r => ({ name: r.title || "", url: r.url || "", folder: r.folder || "" }));
} catch { return []; }
finally { try { fs.unlinkSync(tmp); } catch {} }
}
// --- Main ---
async function main() {
const input = await readInput();
const query = stripKeyword(input.query || "", "search", "s");
const browser = getDefaultBrowser();
if (!browser) {
writeResponse(error("Could not detect default browser"));
return;
}
let bookmarks = [];
if (browser.profilesDir) {
// Firefox
const profiles = getFirefoxProfiles(browser.profilesDir);
for (const p of profiles) bookmarks.push(...findFirefoxBookmarks(p));
} else if (browser.userDataDir) {
// Chromium
const profiles = getChromiumProfiles(browser.userDataDir);
for (const p of profiles) bookmarks.push(...findChromiumBookmarks(p));
}
// Filter
const words = (query || "").toLowerCase().split(/\s+/).filter(Boolean);
let filtered = bookmarks;
if (words.length > 0) {
filtered = bookmarks.filter(bm => {
const hay = `${bm.name} ${bm.url} ${bm.folder}`.toLowerCase();
return words.every(w => hay.includes(w));
});
}
filtered = filtered.slice(0, 50);
if (filtered.length === 0 && query) {
writeResponse(error("No bookmarks found", `No results for "${query}" in ${browser.name}`));
return;
}
if (filtered.length === 0) {
writeResponse(error("No bookmarks", `No bookmarks found in ${browser.name}`));
return;
}
const items = filtered.map((bm, i) => {
let subtitle = bm.url.length > 70 ? bm.url.slice(0, 67) + "..." : bm.url;
if (bm.folder) subtitle += ` · ${bm.folder}`;
subtitle += ` · ${browser.name}`;
return {
id: `bm-${i}`,
title: bm.name || bm.url,
subtitle,
icon: "🔖",
actions: [
{ title: "Open", shortcut: "enter", type: "open_url", url: bm.url },
{ title: "Copy URL", shortcut: "cmd+c", type: "copy", value: bm.url },
],
};
});
writeResponse(list(items));
}
main().catch(e => { try { writeResponse(error("Error", e.message)); } catch(_) {} process.exit(1); });