152 lines
5.0 KiB
Swift
152 lines
5.0 KiB
Swift
import Foundation
|
|
import AppKit
|
|
import os
|
|
|
|
enum BrowserType {
|
|
case safari
|
|
case arc
|
|
case chrome
|
|
case edge
|
|
case firefox
|
|
case brave
|
|
case opera
|
|
case vivaldi
|
|
case orion
|
|
case zen
|
|
|
|
var scriptName: String {
|
|
switch self {
|
|
case .safari: return "safariURL"
|
|
case .arc: return "arcURL"
|
|
case .chrome: return "chromeURL"
|
|
case .edge: return "edgeURL"
|
|
case .firefox: return "firefoxURL"
|
|
case .brave: return "braveURL"
|
|
case .opera: return "operaURL"
|
|
case .vivaldi: return "vivaldiURL"
|
|
case .orion: return "orionURL"
|
|
case .zen: return "zenURL"
|
|
}
|
|
}
|
|
|
|
var bundleIdentifier: String {
|
|
switch self {
|
|
case .safari: return "com.apple.Safari"
|
|
case .arc: return "company.thebrowser.Browser"
|
|
case .chrome: return "com.google.Chrome"
|
|
case .edge: return "com.microsoft.edgemac"
|
|
case .firefox: return "org.mozilla.firefox"
|
|
case .brave: return "com.brave.Browser"
|
|
case .opera: return "com.operasoftware.Opera"
|
|
case .vivaldi: return "com.vivaldi.Vivaldi"
|
|
case .orion: return "com.kagi.kagimacOS"
|
|
case .zen: return "app.zen-browser.zen"
|
|
}
|
|
}
|
|
|
|
var displayName: String {
|
|
switch self {
|
|
case .safari: return "Safari"
|
|
case .arc: return "Arc"
|
|
case .chrome: return "Google Chrome"
|
|
case .edge: return "Microsoft Edge"
|
|
case .firefox: return "Firefox"
|
|
case .brave: return "Brave"
|
|
case .opera: return "Opera"
|
|
case .vivaldi: return "Vivaldi"
|
|
case .orion: return "Orion"
|
|
case .zen: return "Zen Browser"
|
|
}
|
|
}
|
|
|
|
static var allCases: [BrowserType] {
|
|
[.safari, .arc, .chrome, .edge, .brave, .opera, .vivaldi, .orion]
|
|
}
|
|
|
|
static var installedBrowsers: [BrowserType] {
|
|
allCases.filter { browser in
|
|
let workspace = NSWorkspace.shared
|
|
return workspace.urlForApplication(withBundleIdentifier: browser.bundleIdentifier) != nil
|
|
}
|
|
}
|
|
}
|
|
|
|
enum BrowserURLError: Error {
|
|
case scriptNotFound
|
|
case executionFailed
|
|
case browserNotRunning
|
|
case noActiveWindow
|
|
case noActiveTab
|
|
}
|
|
|
|
class BrowserURLService {
|
|
static let shared = BrowserURLService()
|
|
|
|
private let logger = Logger(
|
|
subsystem: "com.prakashjoshipax.VoiceInk",
|
|
category: "browser.applescript"
|
|
)
|
|
|
|
private init() {}
|
|
|
|
func getCurrentURL(from browser: BrowserType) async throws -> String {
|
|
guard let scriptURL = Bundle.main.url(forResource: browser.scriptName, withExtension: "scpt") else {
|
|
logger.error("❌ AppleScript file not found: \(browser.scriptName).scpt")
|
|
throw BrowserURLError.scriptNotFound
|
|
}
|
|
|
|
logger.debug("🔍 Attempting to execute AppleScript for \(browser.displayName)")
|
|
|
|
// Check if browser is running
|
|
if !isRunning(browser) {
|
|
logger.error("❌ Browser not running: \(browser.displayName)")
|
|
throw BrowserURLError.browserNotRunning
|
|
}
|
|
|
|
let task = Process()
|
|
task.launchPath = "/usr/bin/osascript"
|
|
task.arguments = [scriptURL.path]
|
|
|
|
let pipe = Pipe()
|
|
task.standardOutput = pipe
|
|
task.standardError = pipe
|
|
|
|
do {
|
|
logger.debug("▶️ Executing AppleScript for \(browser.displayName)")
|
|
try task.run()
|
|
task.waitUntilExit()
|
|
|
|
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
|
if let output = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) {
|
|
if output.isEmpty {
|
|
logger.error("❌ Empty output from AppleScript for \(browser.displayName)")
|
|
throw BrowserURLError.noActiveTab
|
|
}
|
|
|
|
// Check if output contains error messages
|
|
if output.lowercased().contains("error") {
|
|
logger.error("❌ AppleScript error for \(browser.displayName): \(output)")
|
|
throw BrowserURLError.executionFailed
|
|
}
|
|
|
|
logger.debug("✅ Successfully retrieved URL from \(browser.displayName): \(output)")
|
|
return output
|
|
} else {
|
|
logger.error("❌ Failed to decode output from AppleScript for \(browser.displayName)")
|
|
throw BrowserURLError.executionFailed
|
|
}
|
|
} catch {
|
|
logger.error("❌ AppleScript execution failed for \(browser.displayName): \(error.localizedDescription)")
|
|
throw BrowserURLError.executionFailed
|
|
}
|
|
}
|
|
|
|
func isRunning(_ browser: BrowserType) -> Bool {
|
|
let workspace = NSWorkspace.shared
|
|
let runningApps = workspace.runningApplications
|
|
let isRunning = runningApps.contains { $0.bundleIdentifier == browser.bundleIdentifier }
|
|
logger.debug("\(browser.displayName) running status: \(isRunning)")
|
|
return isRunning
|
|
}
|
|
}
|