mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-11 20:00:23 +01:00
Add scripts for testing Safari bookmarks saving and API key generation.
This commit is contained in:
197
packages/web/tests/bookmarks-save.ts
Normal file
197
packages/web/tests/bookmarks-save.ts
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
/**
|
||||||
|
* Test script to save Safari tabs as bookmarks to Linsa
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* pnpm tsx tests/bookmarks-save.ts
|
||||||
|
*
|
||||||
|
* Requires:
|
||||||
|
* - LINSA_API_KEY environment variable (or create one at /settings)
|
||||||
|
* - Safari running with tabs open
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { execSync } from "node:child_process"
|
||||||
|
|
||||||
|
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 POST /api/api-keys")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SafariTab {
|
||||||
|
title: string
|
||||||
|
url: string
|
||||||
|
windowIndex: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Safari tabs using AppleScript
|
||||||
|
function getSafariTabs(): SafariTab[] {
|
||||||
|
const script = `
|
||||||
|
tell application "Safari"
|
||||||
|
set tabList to {}
|
||||||
|
set windowCount to count of windows
|
||||||
|
repeat with w from 1 to windowCount
|
||||||
|
set tabCount to count of tabs of window w
|
||||||
|
repeat with t from 1 to tabCount
|
||||||
|
set tabTitle to name of tab t of window w
|
||||||
|
set tabURL to URL of tab t of window w
|
||||||
|
set end of tabList to {windowIndex:w, title:tabTitle, url:tabURL}
|
||||||
|
end repeat
|
||||||
|
end repeat
|
||||||
|
return tabList
|
||||||
|
end tell
|
||||||
|
`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
timeout: 10000,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Parse AppleScript output: {{windowIndex:1, title:"...", url:"..."}, ...}
|
||||||
|
const tabs: SafariTab[] = []
|
||||||
|
|
||||||
|
// AppleScript returns records in format: window index:1, title:..., url:...
|
||||||
|
const matches = result.matchAll(
|
||||||
|
/window ?[iI]ndex:(\d+),\s*title:(.*?),\s*url:(.*?)(?=,\s*window|$)/g
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const match of matches) {
|
||||||
|
tabs.push({
|
||||||
|
windowIndex: parseInt(match[1]),
|
||||||
|
title: match[2].trim(),
|
||||||
|
url: match[3].trim(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// If regex didn't work, try simpler line-by-line parsing
|
||||||
|
if (tabs.length === 0) {
|
||||||
|
// Alternative: get just URLs and titles separately
|
||||||
|
const urlScript = `
|
||||||
|
tell application "Safari"
|
||||||
|
set urls to {}
|
||||||
|
repeat with w in windows
|
||||||
|
repeat with t in tabs of w
|
||||||
|
set end of urls to URL of t
|
||||||
|
end repeat
|
||||||
|
end repeat
|
||||||
|
return urls
|
||||||
|
end tell
|
||||||
|
`
|
||||||
|
const titleScript = `
|
||||||
|
tell application "Safari"
|
||||||
|
set titles to {}
|
||||||
|
repeat with w in windows
|
||||||
|
repeat with t in tabs of w
|
||||||
|
set end of titles to name of t
|
||||||
|
end repeat
|
||||||
|
end repeat
|
||||||
|
return titles
|
||||||
|
end tell
|
||||||
|
`
|
||||||
|
|
||||||
|
const urlsRaw = execSync(`osascript -e '${urlScript.replace(/'/g, "'\\''")}'`, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
}).trim()
|
||||||
|
|
||||||
|
const titlesRaw = execSync(`osascript -e '${titleScript.replace(/'/g, "'\\''")}'`, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
}).trim()
|
||||||
|
|
||||||
|
// Parse comma-separated lists
|
||||||
|
const urls = urlsRaw.split(", ").filter(Boolean)
|
||||||
|
const titles = titlesRaw.split(", ").filter(Boolean)
|
||||||
|
|
||||||
|
for (let i = 0; i < urls.length; i++) {
|
||||||
|
tabs.push({
|
||||||
|
windowIndex: 1,
|
||||||
|
title: titles[i] || "",
|
||||||
|
url: urls[i],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tabs
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to get Safari tabs:", error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save a bookmark to Linsa
|
||||||
|
async function saveBookmark(tab: SafariTab, 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 to save ${tab.url}:`, error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to save ${tab.url}:`, error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log("Getting Safari tabs...")
|
||||||
|
const tabs = getSafariTabs()
|
||||||
|
|
||||||
|
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`)
|
||||||
|
|
||||||
|
// Create session tag with timestamp
|
||||||
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)
|
||||||
|
const sessionTag = `session-${timestamp}`
|
||||||
|
|
||||||
|
console.log(`Saving to Linsa with tag: ${sessionTag}`)
|
||||||
|
console.log(`API URL: ${API_URL}`)
|
||||||
|
console.log("")
|
||||||
|
|
||||||
|
let saved = 0
|
||||||
|
let failed = 0
|
||||||
|
|
||||||
|
for (const tab of tabs) {
|
||||||
|
// Skip empty URLs or about: pages
|
||||||
|
if (!tab.url || tab.url.startsWith("about:") || tab.url === "favorites://") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write(` Saving: ${tab.title.slice(0, 50)}... `)
|
||||||
|
const success = await saveBookmark(tab, sessionTag)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
console.log("✓")
|
||||||
|
saved++
|
||||||
|
} else {
|
||||||
|
console.log("✗")
|
||||||
|
failed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("")
|
||||||
|
console.log(`Done! Saved ${saved} bookmarks, ${failed} failed`)
|
||||||
|
console.log(`Session tag: ${sessionTag}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error)
|
||||||
71
packages/web/tests/generate-api-key.ts
Normal file
71
packages/web/tests/generate-api-key.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Generate an API key for a user (for testing)
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* pnpm tsx tests/generate-api-key.ts <user_id>
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* pnpm tsx tests/generate-api-key.ts nikiv
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "dotenv/config"
|
||||||
|
import { getDb } from "../src/db/connection"
|
||||||
|
import { api_keys } from "../src/db/schema"
|
||||||
|
|
||||||
|
const databaseUrl = process.env.DATABASE_URL
|
||||||
|
|
||||||
|
if (!databaseUrl) {
|
||||||
|
console.error("DATABASE_URL is required")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = process.argv[2]
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
console.error("Usage: pnpm tsx tests/generate-api-key.ts <user_id>")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random API key
|
||||||
|
function generateApiKey(): string {
|
||||||
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
let key = "lk_"
|
||||||
|
for (let i = 0; i < 32; i++) {
|
||||||
|
key += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash function
|
||||||
|
async function hashApiKey(key: string): Promise<string> {
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
const data = encoder.encode(key)
|
||||||
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data)
|
||||||
|
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||||||
|
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const db = getDb(databaseUrl!)
|
||||||
|
|
||||||
|
const plainKey = generateApiKey()
|
||||||
|
const keyHash = await hashApiKey(plainKey)
|
||||||
|
|
||||||
|
await db.insert(api_keys).values({
|
||||||
|
user_id: userId,
|
||||||
|
key_hash: keyHash,
|
||||||
|
name: "CLI Generated",
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log("")
|
||||||
|
console.log("API Key generated successfully!")
|
||||||
|
console.log("")
|
||||||
|
console.log("Key (save this, it won't be shown again):")
|
||||||
|
console.log(` ${plainKey}`)
|
||||||
|
console.log("")
|
||||||
|
console.log("Usage:")
|
||||||
|
console.log(` LINSA_API_KEY=${plainKey} pnpm tsx tests/bookmarks-save.ts`)
|
||||||
|
console.log("")
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error)
|
||||||
Reference in New Issue
Block a user