Import Raindrop.io Highlights into Readwise
The output of the import script. Generated with Carbon.now.sh.
UPDATE Readwise now officially supports Raindrop.io! I wholeheartedly recommend using the official integration over my version.
I’ve recently discovered Raindrop.io as a bookmarking/highlighting service. It has been refreshing to use as it exudes polish. The browser extension is nice as it indicates if you’ve bookmarked the page already, shows you highlights on the page, and allows you to highlight the content of the page. The iOS client is also great and does everything I need. The cherry-on-top is that the free plan is very capable and has unlimited highlights per article.
I’ve been a user of Readwise for over two years at this point. It’s an important service for me as it allows me to revisit article’s key points that I’ve highlighted. Readwise has many integrations to import highlights into it. I’ve been using Hypothesis for a while, which has an official Readwise integration.
As I moved over to Raindrop.io, it dawned on me that Readwise doesn’t yet have any official integration with Raindrop.io. I’ve emailed Readwise about this and got the following:
You’re actually one of the first to request an integration with Raindrop.io. I’ve noted your request in our system, so as more users request the integration then it’ll move up in the queue to develop.
I didn’t want to wait around and I noticed that both services have an API…
Looking at both the Readwise API and Raindrop.io API, it seemed fairly straight-forward to connect them up.
It was noted that Readwise de-dupe highlights by title/author/text/sourceurl_, so the API was idempotent if I send the same highlights.
I did hit a couple of snags in building my solution:
nokogiri
to fetch these from the article’s URLMy approach works and only requires to be run periodically. I’ve been toying around with the idea of just having a cron
run it for me hourly.
I didn’t put in annotations from Raindrop.io (as I’m using the free plan for now), but it wouldn’t be hard to add that.
I didn’t bother removing highlights from Readwise when highlights are deleted on Raindrop.io. I noted that this is the default behaviour with Readwise according to their FAQ:
if you delete the highlight in the original source, the highlight will not be deleted in Readwise.
An official integration would be nice, but until then this does the trick.
The following is the Ruby script for importing Raindrop.io highlights into Readwise. You need to acquire your API tokens for both services and substitute them into the placeholders.
This GitHub Gist will be the canonical reference of the import script (and will see updates as needed).
#!/usr/bin/env ruby
require "httparty"
require "nokogiri"
require "open-uri"
require "uri"
RAINDROP_AUTH_TOKEN="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
READWISE_AUTH_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
LAST_SAVED_HIGHLIGHT=000000000 # <- Keeps track of import position (Updates automatically)
def title_from_url(url)
URI.open(url) do |f|
doc = Nokogiri::HTML(f)
title = doc.at_css("title").text
return title
end
end
def domain_from_url(url)
URI.parse(url).host.gsub(/^www\./i, "")
end
def request_raindrop_highlights(page)
HTTParty.get("https://api.raindrop.io/rest/v1/highlights?page=#{page}&perpage=50",
headers: {
"Authorization" => "Bearer #{RAINDROP_AUTH_TOKEN}",
"Content-Type" => "application/json"
},
)
end
def save_highlight_to_readwise(highlight)
HTTParty.post("https://readwise.io/api/v2/highlights/",
headers: {
"Authorization" => "Token #{READWISE_AUTH_TOKEN}",
"Content-Type" => "application/json"
},
body: {
highlights: [
{
text: highlight["text"],
title: title_from_url(highlight["link"]),
author: domain_from_url(highlight["link"]),
source_url: highlight["link"],
category: "articles",
highlighted_at: DateTime.parse(highlight["created"]).iso8601
}
]
}.to_json
)
end
def update_last_saved_highlight_value(last_highlight_value)
puts "Updating last saved highlight value to #{last_highlight_value}"
content = File.read(__FILE__)
updated_content = content.sub(/LAST_SAVED_HIGHLIGHT=\d+/, "LAST_SAVED_HIGHLIGHT=#{last_highlight_value}")
File.write(__FILE__, updated_content)
end
first_highlight_id = nil
page = 0
while true
puts "Processing Raindrop.io highlights from page #{page + 1}"
raindrop_highlights = request_raindrop_highlights(page)["items"]
if raindrop_highlights.empty?
puts "No more highlights to process"
update_last_saved_highlight_value(first_highlight_id)
exit
end
raindrop_highlights.map do |highlight|
puts "-> Importing highlight into Readwise (#{[highlight['link'], highlight['created']].join(', ')})"
saved_highlight = save_highlight_to_readwise(highlight).first
saved_highlight_id = saved_highlight["modified_highlights"].first
first_highlight_id = saved_highlight_id if first_highlight_id.nil?
if saved_highlight_id == LAST_SAVED_HIGHLIGHT
puts "Reached last saved highlight"
update_last_saved_highlight_value(first_highlight_id)
exit
end
end
page += 1
end