A Keyboard Maestro action to save bookmarks to Espial
So this is a little esoteric, but it meets a need I encountered; and it may meet yours if you use Espial, Keyboard Maestro and are on macOS.
For several years I’ve been using Espial a bookmark manager that looks and feels like Pinboard, but is both self-hosted and drama-free1. Espial is easy to setup, stores its data in a comprehensible sqlite database and has an API, which comes in handy when it came to solving the problem I encountered.
Espial offers a JavaScript bookmarklet that can be used to add bookmarks. It works well in Safari, but is tricky in Firefox. In the latter, creating an exception to the popup blockade proved difficult. Even more difficult was getting Firefox to allow me to opt-out of HTTPS-only mode for my Espial server URL which is not served with HTTPS. In lieu of playing around with Firefox settings, I opted to create a Keyboard Maestro macro to save the current page to Espial. I’m going to walk through the prerequisites first, then describe the steps in the macro in details, then finally provide a download link for the macro itself.
Prerequisites
- You will need a functioning installation of Espial
- Create an API key as described in this pull request. I did
stack exec migration -- createapikey --conn espial.sqlite3 --userName my_user_name
. - Create a configuration file
config.yml
at~/.espial/config.yml
:
api_key: your_api_key
server:
url: example.com
port: 3000
- One of the actions in the macro will need a Ruby gem
nokogiri
2 so install withgem install nokogiri
.
The macro in detail
First we’ll need the currently loaded URL, so we will move the cursor to the address bar, make sure the address is selected and copy it. That involves issues ⌘+L, ⌘+A, and ⌘+C in succession. Next we save the system clipboard to a variable url
. The next step is to extract the page title from the URL. I did it using Ruby and nokogiri
but there are other choices out there, of course.
#!/usr/bin/env ruby
require 'nokogiri'
require 'open-uri'
def get_page_title(url)
begin
doc = Nokogiri::HTML(URI.open(url))
title = doc.at_css('title')&.text&.strip
puts title || "No title found"
rescue OpenURI::HTTPError => e
puts "HTTP Error: #{e.message}"
rescue SocketError => e
puts "Network Error: #{e.message}"
rescue StandardError => e
puts "Error: #{e.message}"
end
end
url = ENV['KMVAR_url']
unless url
puts "Error: No URL provided in KMVAR_url"
exit 1
end
get_page_title(url)
We save that output into a variable pageTitle
.
Next, we request additional metadata about that bookmark from the user.
The remaining step is to collect the variables from the dialog and formulate and execute the API call to our Espial instance:
#!/usr/bin/env ruby
require 'net/http'
require 'uri'
require 'json'
require 'yaml'
# Read configuration from YAML file
begin
config = YAML.load_file(File.expand_path('~/.espial/config.yml'))
api_key = config['api_key']
server_url = config['server']['url']
server_port = config['server']['port']
raise "Bad config" unless api_key && server_url && server_port
rescue => e
puts "Error reading config: #{e.message}"
exit 1
end
# Get variables from KM
url = ENV['KMVAR_url']
page_title = ENV['KMVAR_pageTitle']
description = ENV['KMVAR_description']
tags = ENV['KMVAR_tags']
private = ENV['KMVAR_private'] == '1'
to_read = ENV['KMVAR_to_read'] == '1'
# Prepare the request
uri = URI("http://#{server_url}:#{server_port}/api/add")
http = Net::HTTP.new(uri.host, uri.port)
# Prepare the request body
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = "ApiKey #{api_key}"
# Set the request body
request.body = JSON.generate({
url: url,
title: page_title,
description: description,
tags: tags,
private: private,
toread: to_read
})
# Make the request and get the response
begin
response = http.request(request)
puts "Status: #{response.code}"
puts "Response: #{response.body}"
rescue => e
puts "Error: #{e.message}"
end
That’s it! The Bookmark in Espial macro which you can download has a little more involved preparation and more dependencies than many others I use, but dramatically simplifies bookmarking to Espial. I’ve tested it with both Safari and Firefox on the macOS desktop.
-
The End Times have come for the Pinboard.in bookmarking service - Archived version at archive.is, Related discussion on Hacker News ↩︎
-
Nokogiri Ruby gem - parsing XML and HTML documents in Ruby ↩︎