kindle-highlights 1.0.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8965c4c6a5c1a03d4995b880216b34bd7eaa7689
4
- data.tar.gz: 2b8d217f0bef63038fb22aea5a58b4aaf6cfa9f1
3
+ metadata.gz: e4ba7bab3c883c076439b271b56f106f8c03335d
4
+ data.tar.gz: f6f0cc0fbf202a3771a0f2ccc22fc9375ab1cc72
5
5
  SHA512:
6
- metadata.gz: 3071f361fb4c0d03daefbad09800cabae12638776ef1725df0dea8b7aac8d7ecffd33f8d43a8f3643a96051bd0a2bbe75210f3f91a11015528172b58e9c01277
7
- data.tar.gz: 71988265df7a3cb976dcd1dec09144bad05c4b3b3cc906c919bd39376763be02e9df262a92015d62ff510ecbdf4e2ae96f5e649969952910f0970431c73d52fe
6
+ metadata.gz: 72200505594865df64726113c90263a8033591126b5c7e74099e88b1576513522fa83a28dbd277b29de7789d3f12c21e34770e45e96638e49cdc8cca15835ba0
7
+ data.tar.gz: dbb58e9d60ff449202d95561eda889f9b283ac64248b42800f2d7373d5d6e20a168a45b07f93ffeaa175be89a613fddeff6e0b33b798e1efe6567fb58d11df52
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,56 @@
1
+ module KindleHighlights
2
+ class Book
3
+ attr_accessor :asin, :author, :title
4
+
5
+ def self.from_html_elements(html_element:, mechanize_agent:)
6
+ new(
7
+ mechanize_agent: mechanize_agent,
8
+ asin: html_element.attributes["id"].value.squish,
9
+ title: html_element.children.search("h2").first.text.squish,
10
+ author: html_element.children.search("p").first.text.split(":").last.strip.squish
11
+ )
12
+ end
13
+
14
+ def initialize(asin:, author:, title:, mechanize_agent: nil)
15
+ @asin = asin
16
+ @author = author
17
+ @title = title
18
+ @mechanize_agent = mechanize_agent
19
+ end
20
+
21
+ def to_s
22
+ "#{title} by #{author}"
23
+ end
24
+
25
+ def inspect
26
+ "<#{self.class}: #{inspectable_vars}>"
27
+ end
28
+
29
+ def highlights_from_amazon
30
+ return [] unless mechanize_agent.present?
31
+
32
+ @highlights ||= fetch_highlights_from_amazon
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :mechanize_agent
38
+
39
+ def fetch_highlights_from_amazon
40
+ mechanize_agent
41
+ .get("https://read.amazon.com/kp/notebook?captcha_verified=1&asin=#{asin}&contentLimitState=&")
42
+ .search("div#kp-notebook-annotations")
43
+ .children
44
+ .select { |child| child.name == "div" }
45
+ .select { |child| child.children.search("div.kp-notebook-highlight").first.present? }
46
+ .map { |html_elements| Highlight.from_html_elements(book: self, html_elements: html_elements) }
47
+ end
48
+
49
+ def inspectable_vars
50
+ instance_variables
51
+ .select { |ivar| ivar != :@mechanize_agent }
52
+ .map { |ivar| "#{ivar}=#{instance_variable_get(ivar).inspect}" }
53
+ .join(", ")
54
+ end
55
+ end
56
+ end
@@ -2,14 +2,20 @@ module KindleHighlights
2
2
  class Client
3
3
  class CaptchaError < StandardError; end
4
4
  class AuthenticationError < StandardError; end
5
+ class AsinNotFoundError < StandardError; end
5
6
 
6
- attr_writer :mechanize_agent
7
- attr_accessor :kindle_logged_in_page
7
+ KINDLE_LOGIN_PAGE = "https://read.amazon.com/notebook"
8
+ SIGNIN_FORM_IDENTIFIER = "signIn"
9
+ MAX_AUTH_RETRIES = 2
10
+
11
+ attr_writer :mechanize_agent, :kindle_logged_in_page
8
12
 
9
13
  def initialize(email_address:, password:, mechanize_options: {})
10
- @email_address = email_address
11
- @password = password
14
+ @email_address = email_address
15
+ @password = password
12
16
  @mechanize_options = mechanize_options
17
+ @retries = 0
18
+ @kindle_logged_in_page = nil
13
19
  end
14
20
 
15
21
  def books
@@ -17,35 +23,46 @@ module KindleHighlights
17
23
  end
18
24
 
19
25
  def highlights_for(asin)
20
- conditionally_sign_in_to_amazon
26
+ if book = books.detect { |book| book.asin == asin }
27
+ book.highlights_from_amazon
28
+ else
29
+ raise AsinNotFoundError, "Book with ASIN #{asin} not found."
30
+ end
31
+ end
21
32
 
22
- cursor = 0
23
- highlights = []
33
+ private
24
34
 
25
- loop do
26
- # This endpoint includes a `hasMore` field. Unfortunately at the time of this writing is always `false`.
27
- page = mechanize_agent.get("https://kindle.amazon.com/kcw/highlights?asin=#{asin}&cursor=#{cursor}&count=#{BATCH_SIZE}")
28
- items = JSON.parse(page.body).fetch("items", [])
35
+ attr_accessor :email_address, :password, :mechanize_options
36
+ attr_reader :kindle_logged_in_page
29
37
 
30
- break unless items.any?
38
+ def mechanize_agent
39
+ @mechanize_agent ||= initialize_mechanize_agent
40
+ end
31
41
 
32
- highlights.concat(items)
33
- cursor += BATCH_SIZE
42
+ def initialize_mechanize_agent
43
+ mechanize_agent = Mechanize.new
44
+ mechanize_agent.user_agent_alias = Mechanize::AGENT_ALIASES.keys.grep(/\A(Linux|Mac|Windows)/).sample
45
+ mechanize_agent.agent.http.verify_mode = OpenSSL::SSL::VERIFY_NONE
46
+
47
+ mechanize_options.each do |mech_attr, value|
48
+ mechanize_agent.send("#{mech_attr}=", value)
34
49
  end
35
- highlights
50
+ mechanize_agent
36
51
  end
37
52
 
38
- private
53
+ def load_books_from_kindle_account
54
+ conditionally_sign_in_to_amazon
39
55
 
40
- attr_accessor :email_address, :password, :mechanize_options
56
+ kindle_library.map do |book|
57
+ unless book.attributes["id"].blank?
58
+ Book.from_html_elements(html_element: book, mechanize_agent: mechanize_agent)
59
+ end
60
+ end.compact
61
+ end
41
62
 
42
63
  def conditionally_sign_in_to_amazon
43
- if @kindle_logged_in_page.nil?
44
- signin_page = mechanize_agent.get(KINDLE_LOGIN_PAGE)
45
- signin_form = signin_page.form(SIGNIN_FORM_IDENTIFIER)
46
- signin_form.email = email_address
47
- signin_form.password = password
48
- post_signin_page = mechanize_agent.submit(signin_form)
64
+ if login?
65
+ post_signin_page = login_via_mechanize
49
66
 
50
67
  if post_signin_page.search("#ap_captcha_img").any?
51
68
  resolution_url = post_signin_page.link_with(text: /See a new challenge/).resolved_uri.to_s
@@ -57,41 +74,32 @@ module KindleHighlights
57
74
  @kindle_logged_in_page = post_signin_page
58
75
  end
59
76
  end
77
+ rescue AuthenticationError
78
+ retry unless too_many_retries?
60
79
  end
61
80
 
62
- def load_books_from_kindle_account
63
- conditionally_sign_in_to_amazon
64
-
65
- books = {}
66
- highlights_page = mechanize_agent.click(kindle_logged_in_page.link_with(text: /Your Books/))
67
-
68
- loop do
69
- highlights_page.search(".//td[@class='titleAndAuthor']").each do |book|
70
- asin_and_title_element = book.search("a").first
71
- asin = asin_and_title_element.attributes.fetch("href").value.split("/").last
72
- title = asin_and_title_element.inner_html
73
- books[asin] = title
74
- end
81
+ def kindle_library
82
+ @kindle_library ||= @kindle_logged_in_page.search("div#kp-notebook-library").children
83
+ end
75
84
 
76
- break if highlights_page.link_with(text: /Next/).nil?
77
- highlights_page = mechanize_agent.click(highlights_page.link_with(text: /Next/))
78
- end
79
- books
85
+ def login_via_mechanize
86
+ signin_page = mechanize_agent.get(KINDLE_LOGIN_PAGE)
87
+ signin_form = signin_page.form(SIGNIN_FORM_IDENTIFIER)
88
+ signin_form.email = email_address
89
+ signin_form.password = password
90
+ mechanize_agent.submit(signin_form)
80
91
  end
81
92
 
82
- def mechanize_agent
83
- @mechanize_agent ||= initialize_mechanize_agent
93
+ def login?
94
+ @kindle_logged_in_page.blank?
84
95
  end
85
96
 
86
- def initialize_mechanize_agent
87
- mechanize_agent = Mechanize.new
88
- mechanize_agent.user_agent_alias = 'Windows Mozilla'
89
- mechanize_agent.agent.http.verify_mode = OpenSSL::SSL::VERIFY_NONE
97
+ def too_many_retries?
98
+ retry! == MAX_AUTH_RETRIES
99
+ end
90
100
 
91
- mechanize_options.each do |mech_attr, value|
92
- mechanize_agent.send("#{mech_attr}=", value)
93
- end
94
- mechanize_agent
101
+ def retry!
102
+ retries += 1
95
103
  end
96
104
  end
97
105
  end
@@ -0,0 +1,23 @@
1
+ module KindleHighlights
2
+ class Highlight
3
+ attr_accessor :asin, :text, :location
4
+
5
+ def self.from_html_elements(book:, html_elements:)
6
+ new(
7
+ asin: book.asin,
8
+ text: html_elements.children.search("div.kp-notebook-highlight").first.text.squish,
9
+ location: html_elements.children.search("input#kp-annotation-location").first.attributes["value"].value,
10
+ )
11
+ end
12
+
13
+ def initialize(asin:, text:, location:)
14
+ @asin = asin
15
+ @text = text
16
+ @location = location
17
+ end
18
+
19
+ def to_s
20
+ text
21
+ end
22
+ end
23
+ end
@@ -1,10 +1,8 @@
1
1
  require 'rubygems'
2
2
  require 'mechanize'
3
- require 'json'
4
- require 'kindle_highlights/client'
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/string/filters'
5
5
 
6
- module KindleHighlights
7
- KINDLE_LOGIN_PAGE = "http://kindle.amazon.com/login"
8
- SIGNIN_FORM_IDENTIFIER = "signIn"
9
- BATCH_SIZE = 200
10
- end
6
+ require_relative './kindle_highlights/client'
7
+ require_relative './kindle_highlights/book'
8
+ require_relative './kindle_highlights/highlight'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kindle-highlights
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Farkas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-17 00:00:00.000000000 Z
11
+ date: 2017-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mechanize
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 2.7.2
19
+ version: 2.7.5
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 2.7.2
26
+ version: 2.7.5
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -66,14 +66,31 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '5.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  description: Until there is a Kindle API, this will suffice.
70
84
  email: eric@prudentiadigital.com
71
85
  executables: []
72
86
  extensions: []
73
87
  extra_rdoc_files: []
74
88
  files:
89
+ - MIT-LICENSE
75
90
  - lib/kindle_highlights.rb
91
+ - lib/kindle_highlights/book.rb
76
92
  - lib/kindle_highlights/client.rb
93
+ - lib/kindle_highlights/highlight.rb
77
94
  homepage: https://github.com/speric/kindle-highlights
78
95
  licenses:
79
96
  - MIT
@@ -94,9 +111,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
111
  version: '0'
95
112
  requirements: []
96
113
  rubyforge_project:
97
- rubygems_version: 2.2.3
114
+ rubygems_version: 2.4.8
98
115
  signing_key:
99
116
  specification_version: 4
100
117
  summary: Kindle highlights
101
118
  test_files: []
102
- has_rdoc: