Instapaper Kindle Two way Sync

Well today might not be the best day for Marco of Instapaper with the introduction of Reading List feature in iOS and Mac OS X by Apple. But I love Instapaper nonetheless. But one of the annoyances I have is that there is no two way sync with the Kindle. Well there is http://goephemera.com/ but it is unmaintained and no longer works for multiple articles.

So in a binge of insomniac coding I decided to write such a sync. Now, I realize that I am not going to be putting a lot of work into maintaing this so take it as is and improve it as you like.

Somethings you will need:

Leave a comment if you need help. But this does assume you have knowledge of the command line.

require 'rubygems'
require 'oauth'
require 'json'
require "sqlite3"

# Get your key here. http://www.instapaper.com/main/request_oauth_consumer_token
INSTAPAPER_CONSUMER_KEY = 'getyourownkey'
INSTAPAPER_CONSUMER_SECRET = 'getyourownkey'
INSTAPAPER_USERNAME = 'user@mail.com'
INSTAPAPER_PASSWORD = 'password'

KINDLEGEN = "~/Dropbox/KindleGen/kindlegen"

class Instapaper
  Url = "http://www.instapaper.com"

  def initialize(consumer_key, consumer_secret)
    @consumer_key    = consumer_key
    @consumer_secret = consumer_secret
  end

  def authorize(username, password)
    @consumer = OAuth::Consumer.new(@consumer_key, @consumer_secret, {
        :site              => "https://www.instapaper.com",
        :access_token_path => "/api/1/oauth/access_token",
        :http_method => :post
      })

    access_token = @consumer.get_access_token(nil, {}, {
        :x_auth_username => username,
        :x_auth_password => password,
        :x_auth_mode     => "client_auth",
      })

    @access_token = OAuth::AccessToken.new(@consumer, access_token.token, access_token.secret)
  end

  def request(path, params={})
    @access_token.request(:post, "#{Url}#{path}", params)
  end
end


$instapaper = Instapaper.new(INSTAPAPER_CONSUMER_KEY, INSTAPAPER_CONSUMER_SECRET)
$instapaper.authorize(INSTAPAPER_USERNAME, INSTAPAPER_PASSWORD)


$db = SQLite3::Database.new('i2ksync.db')
$db.results_as_hash = true

# status 1. download, 2. still_there 3. archived
$db.execute(%{
   create table if not exists bookmarks (
                id integer PRIMARY KEY,
                title text,
                status text,
                UNIQUE(id))
})

# Create the path if it doesn't exist
path = File.join("/Volumes/Kindle/documents", "_instapaper")
Dir.mkdir(path) unless File.exists?(path)

num_not_there = 0
still_there_ids = []

still_there = $db.execute("select * from bookmarks where status = 2")
still_there.each do |bookmark|
  unless File.exists?(File.join(path, "#{bookmark['title']}.mobi"))
    $instapaper.request("/api/1/bookmarks/archive?bookmark_id=#{bookmark['id']}")
    $db.execute("update bookmarks set status = 3 where id = #{bookmark['id']}")

    num_not_there += 1
  else
    still_there_ids << bookmark['id']
  end
end

articles = JSON.parse($instapaper.request("/api/1/bookmarks/list?limit=#{25 - num_not_there}&have=#{still_there_ids.join(',')}").body)

articles.each do |article|
  next unless article.has_key?('title')

  filename = article['title']
  filename = "Article #{article['bookmark_id']}" unless article['title']

  file_path = "#{filename}.html"

  File.open(File.join(path, file_path), 'w') do |f|
    f << $instapaper.request("/api/1/bookmarks/get_text?bookmark_id=#{article['bookmark_id']}").body
  end

  $db.execute("insert or ignore into bookmarks (id, title, status) values (?,?,?)", article['bookmark_id'], filename, 2)
end

`cd #{path} && find . -name "*html" -exec #{KINDLEGEN} {} \\;`
`cd #{path} && find . -name "*html" -exec rm {} \\;`

Viewed  // 
times