kindle 0.1.3 → 0.7.0.beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.adoc +44 -0
- data/CODE_OF_CONDUCT.adoc +73 -0
- data/Gemfile +14 -2
- data/LICENSE +1 -1
- data/README.adoc +180 -0
- data/bin/kindle +2 -19
- data/features/highlights.feature +117 -0
- data/features/support/aruba.rb +1 -0
- data/features/support/highlight_steps.rb +13 -0
- data/kindle.gemspec +11 -7
- data/lib/kindle.rb +18 -17
- data/lib/kindle/cli.rb +115 -0
- data/lib/kindle/exports/csv.rb +15 -0
- data/lib/kindle/exports/json.rb +12 -0
- data/lib/kindle/exports/markdown.rb +18 -0
- data/lib/kindle/migrations/base_migration.rb +22 -0
- data/lib/kindle/migrations/initializer.rb +13 -0
- data/lib/kindle/models/book.rb +26 -0
- data/lib/kindle/models/highlight.rb +24 -0
- data/lib/kindle/parser/agent.rb +13 -0
- data/lib/kindle/parser/annotations.rb +130 -0
- data/lib/kindle/remote/book.rb +14 -0
- data/lib/kindle/remote/highlight.rb +12 -0
- data/lib/kindle/settings.rb +51 -0
- data/templates/database.yml +11 -0
- data/test/cli_test.rb +0 -0
- data/test/highlights_test.rb +13 -0
- data/test/kindle_test.rb +17 -0
- metadata +98 -17
- data/.rvmrc +0 -1
- data/Changelog +0 -24
- data/README.md +0 -19
- data/lib/kindle/highlight.rb +0 -13
- data/lib/kindle/highlights_parser.rb +0 -105
- data/lib/kindle/version.rb +0 -3
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            require 'aruba/cucumber'
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            require "kindle"
         | 
| 2 | 
            +
            require 'cucumber/timecop'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            Given(/^a database exists$/) do
         | 
| 5 | 
            +
              Kindle::Models::Book.delete_all
         | 
| 6 | 
            +
              Kindle::Models::Highlight.delete_all
         | 
| 7 | 
            +
              book = Kindle::Models::Book.create! title: "Zen", author: "Monk", highlight_count: 1
         | 
| 8 | 
            +
              highlight = book.highlights.create! highlight: "Reach for enlightenment"
         | 
| 9 | 
            +
            end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            Given(/^time is frozen$/) do
         | 
| 12 | 
            +
              Timecop.freeze(Time.local(1990))
         | 
| 13 | 
            +
            end
         | 
    
        data/kindle.gemspec
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # -*- encoding: utf-8 -*-
         | 
| 2 2 | 
             
            $:.push File.expand_path("../lib", __FILE__)
         | 
| 3 | 
            -
             | 
| 3 | 
            +
            require_relative "lib/kindle"
         | 
| 4 4 |  | 
| 5 5 | 
             
            Gem::Specification.new do |s|
         | 
| 6 6 | 
             
              s.name        = "kindle"
         | 
| @@ -8,9 +8,9 @@ Gem::Specification.new do |s| | |
| 8 8 | 
             
              s.platform    = Gem::Platform::RUBY
         | 
| 9 9 | 
             
              s.authors     = ["Matt Petty"]
         | 
| 10 10 | 
             
              s.email       = ["matt@kizmeta.com"]
         | 
| 11 | 
            -
              s.homepage    = ""
         | 
| 12 | 
            -
              s.summary     = %q{Manage your kindle highlights with ruby}
         | 
| 13 | 
            -
              s.description = %q{Manage your  | 
| 11 | 
            +
              s.homepage    = "https://github.com/lodestone/kindle"
         | 
| 12 | 
            +
              s.summary     = %q{Manage your kindle highlights with ruby, output in JSON, Markdown, and CSV formats}
         | 
| 13 | 
            +
              s.description = %q{Manage your Amazon Kindle highlights: Sync and cache to an ActiveRecord database and output in various formats}
         | 
| 14 14 |  | 
| 15 15 | 
             
              s.rubyforge_project = "kindle"
         | 
| 16 16 |  | 
| @@ -18,7 +18,11 @@ Gem::Specification.new do |s| | |
| 18 18 | 
             
              s.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")
         | 
| 19 19 | 
             
              s.executables   = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
         | 
| 20 20 | 
             
              s.require_paths = ["lib"]
         | 
| 21 | 
            -
              s. | 
| 22 | 
            -
              s. | 
| 23 | 
            -
              s. | 
| 21 | 
            +
              s.add_runtime_dependency "gli"
         | 
| 22 | 
            +
              s.add_runtime_dependency "sqlite3"
         | 
| 23 | 
            +
              s.add_runtime_dependency "activerecord", "~>5.0.0"
         | 
| 24 | 
            +
              s.add_runtime_dependency "nokogiri"
         | 
| 25 | 
            +
              s.add_runtime_dependency "mechanize"
         | 
| 26 | 
            +
              s.add_runtime_dependency "rainbow"
         | 
| 27 | 
            +
              s.add_runtime_dependency "pry"
         | 
| 24 28 | 
             
            end
         | 
    
        data/lib/kindle.rb
    CHANGED
    
    | @@ -1,20 +1,21 @@ | |
| 1 | 
            -
            require  | 
| 2 | 
            -
             | 
| 3 | 
            -
            require_relative  | 
| 4 | 
            -
            require_relative  | 
| 1 | 
            +
            require "active_record"
         | 
| 2 | 
            +
            require_relative "kindle/settings"
         | 
| 3 | 
            +
            require_relative "kindle/migrations/initializer"
         | 
| 4 | 
            +
            require_relative "kindle/migrations/base_migration"
         | 
| 5 | 
            +
            require_relative "kindle/models/highlight"
         | 
| 6 | 
            +
            require_relative "kindle/models/book"
         | 
| 7 | 
            +
            require_relative "kindle/parser/agent"
         | 
| 8 | 
            +
            require_relative "kindle/parser/annotations"
         | 
| 9 | 
            +
            require_relative "kindle/remote/book"
         | 
| 10 | 
            +
            require_relative "kindle/remote/highlight"
         | 
| 11 | 
            +
            require_relative "kindle/exports/markdown"
         | 
| 12 | 
            +
            require_relative "kindle/exports/json"
         | 
| 13 | 
            +
            require_relative "kindle/exports/csv"
         | 
| 5 14 |  | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
              class Highlights
         | 
| 9 | 
            -
             | 
| 10 | 
            -
                def initialize(options = {})
         | 
| 11 | 
            -
                  options.each { |k,v| instance_variable_set("@#{k}", v) }
         | 
| 12 | 
            -
                end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
                def fetch_highlights
         | 
| 15 | 
            -
                  HighlightsParser.new(login: @login, password: @password).get_highlights
         | 
| 16 | 
            -
                end
         | 
| 17 | 
            -
             | 
| 18 | 
            -
              end
         | 
| 15 | 
            +
            # TODO: Handle multiple environments
         | 
| 16 | 
            +
            ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: Kindle::Settings::KINDLE_DATABASE_FILENAME)
         | 
| 19 17 |  | 
| 18 | 
            +
            module Kindle
         | 
| 19 | 
            +
              VERSION = "0.7.0.beta2"
         | 
| 20 | 
            +
              include Models
         | 
| 20 21 | 
             
            end
         | 
    
        data/lib/kindle/cli.rb
    ADDED
    
    | @@ -0,0 +1,115 @@ | |
| 1 | 
            +
            require_relative "../kindle"
         | 
| 2 | 
            +
            require "pry"
         | 
| 3 | 
            +
            require "gli"
         | 
| 4 | 
            +
            require "rainbow"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            # TODO: Create a test and pull request for this monkey patch
         | 
| 7 | 
            +
            #  that handles an empty file without blowing up.
         | 
| 8 | 
            +
            module GLI
         | 
| 9 | 
            +
              module AppSupport
         | 
| 10 | 
            +
                def parse_config # :nodoc:
         | 
| 11 | 
            +
                  config = { 'commands' => {} }
         | 
| 12 | 
            +
                  if @config_file && File.exist?(@config_file)
         | 
| 13 | 
            +
                    require 'yaml'
         | 
| 14 | 
            +
                    yaml = YAML.load(File.open(@config_file).read)
         | 
| 15 | 
            +
                    config.merge!(yaml||{})
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                  config
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            module Kindle
         | 
| 23 | 
            +
              class CLI
         | 
| 24 | 
            +
                extend GLI::App
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                sort_help :manually
         | 
| 27 | 
            +
                version Kindle::VERSION
         | 
| 28 | 
            +
                hide_commands_without_desc true
         | 
| 29 | 
            +
                config_file ".kindle/kindlerc.yml"
         | 
| 30 | 
            +
                program_desc Rainbow("Fetch and query your Amazon Kindle Highlights").cyan
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                flag [:username, :u]
         | 
| 33 | 
            +
                flag [:password, :p]
         | 
| 34 | 
            +
                flag [:domain,   :d], default_value: "amazon.com"
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                # NOTE: Commenting out these descriptions to *hide* the option from help
         | 
| 37 | 
            +
                # desc "Initialize the highlights database"
         | 
| 38 | 
            +
                # long_desc "Creates a SQLITE3 database to store/cache your highlights"
         | 
| 39 | 
            +
                command :initdb do |c|
         | 
| 40 | 
            +
                  c.action do |global_options, options, args|
         | 
| 41 | 
            +
                    begin
         | 
| 42 | 
            +
                      puts Rainbow("\nInitializing the database...").green
         | 
| 43 | 
            +
                      Kindle::Migrations::Initializer.new
         | 
| 44 | 
            +
                    rescue ActiveRecord::StatementInvalid
         | 
| 45 | 
            +
                      puts Rainbow("Looks like the database is already created, skipping...").red
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                desc Rainbow("Initialize the database and config files").yellow
         | 
| 51 | 
            +
                long_desc <<~EOD
         | 
| 52 | 
            +
                  Creates the ~/.kindle/kindle.db SQLITE file
         | 
| 53 | 
            +
                  Creates the ~/.kindle directory and a default config file
         | 
| 54 | 
            +
                EOD
         | 
| 55 | 
            +
                command :init do |c|
         | 
| 56 | 
            +
                  c.action do |global_options, options, args|
         | 
| 57 | 
            +
                    commands[:initdb].execute(global_options, options, args)
         | 
| 58 | 
            +
                    commands[:initconfig].execute(global_options, options, args)
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                desc Rainbow("Export Highlights as JSON, CSV, or Markdown").yellow
         | 
| 63 | 
            +
                long_desc "Output your highlights database in JSON, CSV, or Markdown format"
         | 
| 64 | 
            +
                command :highlights do |highlights|
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  highlights.desc Rainbow("Refresh the database of highlights").yellow
         | 
| 67 | 
            +
                  highlights.long_desc "Scrape your current Kindle highlights and update the local database"
         | 
| 68 | 
            +
                  highlights.command :update do |update|
         | 
| 69 | 
            +
                    update.action do
         | 
| 70 | 
            +
                      Kindle::Parser::Annotations.new
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  highlights.desc Rainbow("Export highlights and books as JSON").yellow
         | 
| 75 | 
            +
                  highlights.command :json do |json|
         | 
| 76 | 
            +
                    json.action do
         | 
| 77 | 
            +
                      puts Kindle::Exports::Json.new(Kindle::Models::Highlight.includes(:book).all)
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  highlights.desc Rainbow("Export highlights and books as CSV").yellow
         | 
| 82 | 
            +
                  highlights.command :csv do |csv|
         | 
| 83 | 
            +
                    csv.action do
         | 
| 84 | 
            +
                      puts Kindle::Exports::Csv.new(Kindle::Models::Highlight.includes(:book).all)
         | 
| 85 | 
            +
                    end
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  highlights.desc Rainbow("Export highlights and books as Markdown").yellow
         | 
| 89 | 
            +
                  highlights.command :markdown do |markdown|
         | 
| 90 | 
            +
                    markdown.action do
         | 
| 91 | 
            +
                      puts Kindle::Exports::Markdown.new(Kindle::Models::Book.order("title ASC").all)
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                desc Rainbow("Open an interactive console").yellow
         | 
| 97 | 
            +
                long_desc %{
         | 
| 98 | 
            +
                  Open an interactive console with both Book and Highlight objects available.
         | 
| 99 | 
            +
                }
         | 
| 100 | 
            +
                command :console do |c|
         | 
| 101 | 
            +
                  c.action do |global_options,options,args|
         | 
| 102 | 
            +
                    Pry.start Kindle, prompt: [proc { "Kindle :) " }]
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                # NOTE: Fuckery to hide the auto generated actions:
         | 
| 107 | 
            +
                helpcmd = commands[:help]
         | 
| 108 | 
            +
                helpcmd.instance_variable_set("@description", Rainbow(helpcmd.instance_variable_get("@description")).yellow)
         | 
| 109 | 
            +
                commands[:initconfig].instance_variable_set("@description", nil)
         | 
| 110 | 
            +
                commands[:initconfig].instance_variable_set("@hide_commands_without_desc", true)
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                exit run(ARGV)
         | 
| 113 | 
            +
             | 
| 114 | 
            +
              end
         | 
| 115 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            module Kindle
         | 
| 2 | 
            +
              module Exports
         | 
| 3 | 
            +
                class Csv
         | 
| 4 | 
            +
                  def initialize(highlights)
         | 
| 5 | 
            +
                    @document = "highlight,book title,book author,book asin\n"
         | 
| 6 | 
            +
                    highlights.each do |h|
         | 
| 7 | 
            +
                      @document << "\"#{h.highlight}\",\"#{h.book.title}\",\"#{h.book.author}\",#{h.book.asin}\n"
         | 
| 8 | 
            +
                    end
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
                  def to_s
         | 
| 11 | 
            +
                    @document
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            module Kindle
         | 
| 2 | 
            +
              module Exports
         | 
| 3 | 
            +
                class Markdown
         | 
| 4 | 
            +
                  def initialize(books)
         | 
| 5 | 
            +
                    @document = "## Kindle Highlights\n\n"
         | 
| 6 | 
            +
                    books.each do |book|
         | 
| 7 | 
            +
                      @document << "\n### #{book.title} by #{book.author}\n\n"
         | 
| 8 | 
            +
                      book.highlights.each do |highlight|
         | 
| 9 | 
            +
                        @document << "> #{highlight.highlight}\n\n"
         | 
| 10 | 
            +
                      end
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                  def to_s
         | 
| 14 | 
            +
                    @document
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            class CreateBaseStructure < ActiveRecord::Migration[5.0]
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              def self.up
         | 
| 4 | 
            +
                create_table :books do |t|
         | 
| 5 | 
            +
                  t.string :asin, :title, :author
         | 
| 6 | 
            +
                  t.integer :highlight_count
         | 
| 7 | 
            +
                  t.timestamps
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
                create_table :highlights do |t|
         | 
| 10 | 
            +
                  t.text :highlight
         | 
| 11 | 
            +
                  t.string :amazon_id
         | 
| 12 | 
            +
                  t.integer :book_id
         | 
| 13 | 
            +
                  t.timestamps
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def self.down
         | 
| 18 | 
            +
                drop_table :books
         | 
| 19 | 
            +
                drop_table :highlights
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            require_relative "base_migration"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Kindle
         | 
| 4 | 
            +
              module Migrations
         | 
| 5 | 
            +
                class Initializer
         | 
| 6 | 
            +
                  def initialize
         | 
| 7 | 
            +
                    # ActiveRecord::Base.configurations = YAML::load(IO.read(ENV['HOME']+'/.kindle/database.yml'))
         | 
| 8 | 
            +
                    # ActiveRecord::Base.establish_connection("development")
         | 
| 9 | 
            +
                    CreateBaseStructure.migrate(:up)
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            module Kindle
         | 
| 2 | 
            +
              module Models
         | 
| 3 | 
            +
                class Book < ActiveRecord::Base
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  attribute :asin, :string
         | 
| 6 | 
            +
                  attribute :highlight_count, :integer
         | 
| 7 | 
            +
                  attribute :title, :string
         | 
| 8 | 
            +
                  attribute :author, :string
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  has_many :highlights
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  # def initialize(asin, options = {})
         | 
| 13 | 
            +
                  #   @asin            = asin
         | 
| 14 | 
            +
                  #   @title           = options[:title]
         | 
| 15 | 
            +
                  #   @author          = options[:author]
         | 
| 16 | 
            +
                  #   @highlight_count = options[:highlight_count]
         | 
| 17 | 
            +
                  #   @highlights      = []
         | 
| 18 | 
            +
                  # end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  # def to_csv
         | 
| 21 | 
            +
                  #   "#{id},#{asin},#{author},#{title},#{highlight},highlight_count"
         | 
| 22 | 
            +
                  # end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            module Kindle
         | 
| 2 | 
            +
              module Models
         | 
| 3 | 
            +
                class Highlight < ActiveRecord::Base
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  attribute :amazon_id, :integer
         | 
| 6 | 
            +
                  attribute :highlight, :text
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  belongs_to :book
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  # def to_csv
         | 
| 11 | 
            +
                  #   "#{id},#{asin},#{author},#{title},#{highlight},highlight_count"
         | 
| 12 | 
            +
                  # end
         | 
| 13 | 
            +
                  #
         | 
| 14 | 
            +
                  # def to_json
         | 
| 15 | 
            +
                  #   {id: id, highlight: highlight, asin: asin, title: title, author: author, highlight_count: highlight_count}.to_json
         | 
| 16 | 
            +
                  # end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  # def as_json
         | 
| 19 | 
            +
                  #   {id: id, highlight: highlight, asin: book.asin, title: book.title, author: book.author}
         | 
| 20 | 
            +
                  # end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
            end
         | 
| @@ -0,0 +1,130 @@ | |
| 1 | 
            +
            require "mechanize"
         | 
| 2 | 
            +
            require "nokogiri"
         | 
| 3 | 
            +
            require "json"
         | 
| 4 | 
            +
            require_relative "agent"
         | 
| 5 | 
            +
            require_relative "../remote/highlight"
         | 
| 6 | 
            +
            require_relative "../remote/book"
         | 
| 7 | 
            +
            require_relative "../models/highlight"
         | 
| 8 | 
            +
            require_relative "../models/book"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            module Kindle
         | 
| 11 | 
            +
              module Parser
         | 
| 12 | 
            +
                class Annotations
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  SETTINGS = Kindle::Settings.new
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  attr_accessor :page, :highlights
         | 
| 17 | 
            +
                  attr_reader :books
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def initialize(options={})
         | 
| 20 | 
            +
                    @username   = SETTINGS.username || options[:username]
         | 
| 21 | 
            +
                    @password   = SETTINGS.password || options[:password]
         | 
| 22 | 
            +
                    @books      = []
         | 
| 23 | 
            +
                    @highlights = []
         | 
| 24 | 
            +
                    load_highlights
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def load_highlights
         | 
| 28 | 
            +
                    login
         | 
| 29 | 
            +
                    fetch_highlights
         | 
| 30 | 
            +
                    collect_authoritative_highlights
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  private
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def agent
         | 
| 36 | 
            +
                    @agent ||= Kindle::Parser::Agent.new.agent
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def login
         | 
| 40 | 
            +
                    @page = agent.get(SETTINGS.url + "/login")
         | 
| 41 | 
            +
                    login_form = page.forms.first
         | 
| 42 | 
            +
                    login_form.email    = @username
         | 
| 43 | 
            +
                    login_form.password = @password
         | 
| 44 | 
            +
                    @page = login_form.submit
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  # TODO: Handle CAPTCHA
         | 
| 48 | 
            +
                  # def handle_captcha
         | 
| 49 | 
            +
                  #   if page.css("#ap_captcha_img")
         | 
| 50 | 
            +
                  #     puts "CAPTCHA LOGIN ERROR"
         | 
| 51 | 
            +
                  #     save_and_open_page
         | 
| 52 | 
            +
                  #     p page.link_with(text: /See a new challenge/).resolved_uri.to_s
         | 
| 53 | 
            +
                  #     agent.cookie_jar.save("cookies.txt", format: :cookiestxt)
         | 
| 54 | 
            +
                  #   end
         | 
| 55 | 
            +
                  #   save_and_open_page
         | 
| 56 | 
            +
                  #   rescue => exception
         | 
| 57 | 
            +
                  #   end
         | 
| 58 | 
            +
                  # end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def collect_authoritative_highlights
         | 
| 61 | 
            +
                    # NOTE: This fetch may fail if the highlight count is realy large.
         | 
| 62 | 
            +
                    books.each do |book|
         | 
| 63 | 
            +
                      kb = Kindle::Book.find_or_create_by(asin: book.asin, title: book.title, author: book.author)
         | 
| 64 | 
            +
                      if kb.highlight_count != book.highlight_count
         | 
| 65 | 
            +
                        kb.highlight_count = book.highlight_count
         | 
| 66 | 
            +
                        kb.save!
         | 
| 67 | 
            +
                      end
         | 
| 68 | 
            +
                      url = "#{SETTINGS.url}/kcw/highlights?asin=#{book.asin}&cursor=0&count=#{book.highlight_count}"
         | 
| 69 | 
            +
                      bpage = agent.get url
         | 
| 70 | 
            +
                      items = JSON.parse(bpage.body).fetch("items", [])
         | 
| 71 | 
            +
                      book.highlights = items.map do |item|
         | 
| 72 | 
            +
                        kb.highlights.find_or_create_by(highlight: item["highlight"])
         | 
| 73 | 
            +
                        # TODO: FIXME: amazon_id: item["embeddedId"]
         | 
| 74 | 
            +
                      end
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  def fetch_highlights(state={current_upcoming: [], asins: []})
         | 
| 79 | 
            +
                    page = agent.get("#{SETTINGS.url}/your_highlights")
         | 
| 80 | 
            +
                    initialize_state_with_page state, page
         | 
| 81 | 
            +
                    highlights = extract_highlights_from_page(page, state)
         | 
| 82 | 
            +
                    begin
         | 
| 83 | 
            +
                      page = get_the_next_page(state, highlights.flatten)
         | 
| 84 | 
            +
                      new_highlights = extract_highlights_from_page(page, state)
         | 
| 85 | 
            +
                      highlights << new_highlights
         | 
| 86 | 
            +
                    end until new_highlights.length == 0
         | 
| 87 | 
            +
                    highlights.flatten
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  def extract_highlights_from_page(page, state)
         | 
| 91 | 
            +
                    return [] if page.css(".yourHighlight").length == 0
         | 
| 92 | 
            +
                    page.css(".yourHighlight").map do |hl|
         | 
| 93 | 
            +
                      parse_highlight(hl, state)
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  def initialize_state_with_page(state, page)
         | 
| 98 | 
            +
                    return if (page/".yourHighlight").length == 0
         | 
| 99 | 
            +
                    state[:current_asin] = (page/".yourHighlightsHeader").first.attributes["id"].value.split("_").first
         | 
| 100 | 
            +
                    state[:current_upcoming] = (page/".upcoming").first.text.split(',') rescue []
         | 
| 101 | 
            +
                    state[:title] = (page/".yourHighlightsHeader .title").text.to_s.strip
         | 
| 102 | 
            +
                    state[:author] = (page/".yourHighlightsHeader .author").text.to_s.strip
         | 
| 103 | 
            +
                    highlights_on_page = (page/".yourHighlightsHeader")
         | 
| 104 | 
            +
                    state[:current_offset] = (highlights_on_page.collect{|h| h.attributes['id'].value }).first.split('_').last
         | 
| 105 | 
            +
                    highlight_count = (highlights_on_page/".highlightCount#{state[:current_asin]}").text.to_i
         | 
| 106 | 
            +
                    state[:current_number_of_highlights] = highlight_count
         | 
| 107 | 
            +
                    @books << Kindle::Remote::Book.new(state[:current_asin], {author: state[:author], title: state[:title], highlight_count: highlight_count})
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  def get_the_next_page(state, previously_extracted_highlights = [])
         | 
| 111 | 
            +
                    asins           = previously_extracted_highlights.map(&:asin).uniq
         | 
| 112 | 
            +
                    asins_string    = asins.collect { |asin| "used_asins[]=#{asin}" } * '&'
         | 
| 113 | 
            +
                    upcoming_string = state[:current_upcoming].map { |l| "upcoming_asins[]=#{l}" } * '&'
         | 
| 114 | 
            +
                    url = "#{SETTINGS.url}/your_highlights/next_book?#{asins_string}¤t_offset=#{state[:current_offset]}&#{upcoming_string}"
         | 
| 115 | 
            +
                    ajax_headers = { 'X-Requested-With' => 'XMLHttpRequest', 'Host' => "kindle.#{SETTINGS.domain}" }
         | 
| 116 | 
            +
                    page = agent.get(url,[],"#{SETTINGS.url}/your_highlight", ajax_headers)
         | 
| 117 | 
            +
                    initialize_state_with_page state, page
         | 
| 118 | 
            +
                    page
         | 
| 119 | 
            +
                  end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                  def parse_highlight(hl, state)
         | 
| 122 | 
            +
                    highlight_id   = hl.xpath('//*[@id="annotation_id"]').first["value"]
         | 
| 123 | 
            +
                    highlight_text = (hl/".highlight").text
         | 
| 124 | 
            +
                    asin           = (hl/".asin").text
         | 
| 125 | 
            +
                    highlight = Kindle::Remote::Highlight.new(amazon_id: highlight_id, highlight: highlight_text, asin: asin)
         | 
| 126 | 
            +
                    highlight
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
                end
         | 
| 129 | 
            +
              end
         | 
| 130 | 
            +
            end
         |