mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-11 20:00:23 +01:00
132 lines
3.3 KiB
TypeScript
132 lines
3.3 KiB
TypeScript
/**
|
|
* Save Safari tabs as bookmarks to Linsa
|
|
*
|
|
* Usage:
|
|
* LINSA_API_KEY=lk_xxx pnpm tsx tests/bookmarks-save.ts
|
|
*
|
|
* Or via flow:
|
|
* f save-tabs
|
|
*/
|
|
|
|
import { executeJxa } from "@nikiv/ts-utils"
|
|
|
|
const API_URL = process.env.LINSA_API_URL || "http://localhost:5613"
|
|
const API_KEY = process.env.LINSA_API_KEY
|
|
|
|
if (!API_KEY) {
|
|
console.error("Error: LINSA_API_KEY environment variable is required")
|
|
console.error("Generate one at /settings or via: f gen-api-key")
|
|
process.exit(1)
|
|
}
|
|
|
|
type LocalTab = {
|
|
uuid: string
|
|
title: string
|
|
url: string
|
|
window_id: number
|
|
index: number
|
|
is_local: boolean
|
|
}
|
|
|
|
async function fetchSafariTabs(): Promise<LocalTab[]> {
|
|
return executeJxa(`
|
|
const safari = Application("com.apple.Safari");
|
|
const tabs = [];
|
|
safari.windows().map(window => {
|
|
const windowTabs = window.tabs();
|
|
if (windowTabs) {
|
|
return windowTabs.map(tab => {
|
|
tabs.push({
|
|
uuid: window.id() + '-' + tab.index(),
|
|
title: tab.name(),
|
|
url: tab.url() || '',
|
|
window_id: window.id(),
|
|
index: tab.index(),
|
|
is_local: true
|
|
});
|
|
})
|
|
}
|
|
});
|
|
return tabs;
|
|
`)
|
|
}
|
|
|
|
async function saveBookmark(tab: LocalTab, sessionTag: string): Promise<boolean> {
|
|
try {
|
|
const response = await fetch(`${API_URL}/api/bookmarks`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
url: tab.url,
|
|
title: tab.title,
|
|
tags: `safari,${sessionTag}`,
|
|
api_key: API_KEY,
|
|
}),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json()
|
|
console.error(` ✗ Failed: ${error.error || response.status}`)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
} catch (error) {
|
|
console.error(` ✗ Error:`, error instanceof Error ? error.message : error)
|
|
return false
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
console.log("Getting Safari tabs...")
|
|
const tabs = await fetchSafariTabs()
|
|
|
|
if (tabs.length === 0) {
|
|
console.log("No Safari tabs found. Make sure Safari is running with tabs open.")
|
|
return
|
|
}
|
|
|
|
console.log(`Found ${tabs.length} tabs across ${new Set(tabs.map((t) => t.window_id)).size} windows`)
|
|
|
|
// Create session tag with timestamp
|
|
const date = new Date()
|
|
const timestamp = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}-${String(date.getHours()).padStart(2, "0")}${String(date.getMinutes()).padStart(2, "0")}`
|
|
const sessionTag = `session-${timestamp}`
|
|
|
|
console.log(`\nSaving to Linsa with tag: ${sessionTag}`)
|
|
console.log(`API URL: ${API_URL}`)
|
|
console.log("")
|
|
|
|
let saved = 0
|
|
let skipped = 0
|
|
let failed = 0
|
|
|
|
for (const tab of tabs) {
|
|
// Skip empty URLs, about: pages, favorites
|
|
if (!tab.url || tab.url.startsWith("about:") || tab.url === "favorites://") {
|
|
skipped++
|
|
continue
|
|
}
|
|
|
|
const shortTitle = tab.title.length > 50 ? tab.title.slice(0, 47) + "..." : tab.title
|
|
process.stdout.write(` ${shortTitle} `)
|
|
|
|
const success = await saveBookmark(tab, sessionTag)
|
|
|
|
if (success) {
|
|
console.log("✓")
|
|
saved++
|
|
} else {
|
|
failed++
|
|
}
|
|
}
|
|
|
|
console.log("")
|
|
console.log(`Done! Saved: ${saved}, Skipped: ${skipped}, Failed: ${failed}`)
|
|
console.log(`Session tag: ${sessionTag}`)
|
|
}
|
|
|
|
main().catch(console.error)
|