kindle-notebook 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bec53d52818fd1e01eb99bba9b302edf0cf2b9b688f64224f1d624ac785f86ff
4
+ data.tar.gz: 7dcc6e513cb7c1f62abee9607f34fbfd488d6925458fe7c051e67abfd4604bfd
5
+ SHA512:
6
+ metadata.gz: 69f552342621a69d601ddea394b2c7ab3c85934d92d363b5c8fbeac9d7c6dbf008afa68d249892d17b32e8542ab7d5100c6efcfdd132fd0444941e550844abe5
7
+ data.tar.gz: 7c525530ddfefea76be5b3c88bcce5a936858e80b118c7d9d92a22429b9cc4aac45b371791c9df16327f47be7d75d31c50c238c7e39f8d450094dd0f9fc9fa8f
data/.env_sample ADDED
@@ -0,0 +1,7 @@
1
+ AMAZON_EMAIL=
2
+ AMAZON_PASSWORD=
3
+ HEADLESS_MODE=true
4
+ KINDLE_READER_URL=https://read.amazon.com/
5
+ MAX_HIGHLIGHT_WORDS=3
6
+ MIN_HIGHLIGHT_WORDS=1
7
+ SELENIUM_DRIVER=firefox
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,24 @@
1
+ require:
2
+ - rubocop-rake
3
+ - rubocop-rspec
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 2.6
7
+
8
+ Naming/FileName:
9
+ Exclude:
10
+ - lib/kindle-notebook.rb
11
+
12
+ Style/Documentation:
13
+ Enabled: false
14
+
15
+ Style/StringLiterals:
16
+ Enabled: true
17
+ EnforcedStyle: double_quotes
18
+
19
+ Style/StringLiteralsInInterpolation:
20
+ Enabled: true
21
+ EnforcedStyle: double_quotes
22
+
23
+ Layout/LineLength:
24
+ Max: 120
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at bncvr@outlook.com. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in kindle-notebook.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ gem "rspec", "~> 3.0"
10
+ gem "rubocop", "~> 1.21"
11
+ gem "rubocop-rake"
12
+ gem "rubocop-rspec"
data/Gemfile.lock ADDED
@@ -0,0 +1,108 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ kindle-notebook (0.2.1)
5
+ capybara (~> 3.39)
6
+ capybara-sessionkeeper (~> 0.2.0)
7
+ selenium-webdriver (~> 4.9)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ addressable (2.8.4)
13
+ public_suffix (>= 2.0.2, < 6.0)
14
+ ast (2.4.2)
15
+ capybara (3.39.0)
16
+ addressable
17
+ matrix
18
+ mini_mime (>= 0.1.3)
19
+ nokogiri (~> 1.8)
20
+ rack (>= 1.6.0)
21
+ rack-test (>= 0.6.3)
22
+ regexp_parser (>= 1.5, < 3.0)
23
+ xpath (~> 3.2)
24
+ capybara-sessionkeeper (0.2.0)
25
+ capybara
26
+ selenium-webdriver
27
+ coderay (1.1.3)
28
+ diff-lcs (1.5.0)
29
+ dotenv (2.8.1)
30
+ json (2.6.3)
31
+ matrix (0.4.2)
32
+ method_source (1.0.0)
33
+ mini_mime (1.1.2)
34
+ nokogiri (1.14.3-x86_64-linux)
35
+ racc (~> 1.4)
36
+ parallel (1.22.1)
37
+ parser (3.2.0.0)
38
+ ast (~> 2.4.1)
39
+ pry (0.14.2)
40
+ coderay (~> 1.1)
41
+ method_source (~> 1.0)
42
+ public_suffix (5.0.1)
43
+ racc (1.6.2)
44
+ rack (3.0.7)
45
+ rack-test (2.1.0)
46
+ rack (>= 1.3)
47
+ rainbow (3.1.1)
48
+ rake (13.0.6)
49
+ regexp_parser (2.6.2)
50
+ rexml (3.2.5)
51
+ rspec (3.12.0)
52
+ rspec-core (~> 3.12.0)
53
+ rspec-expectations (~> 3.12.0)
54
+ rspec-mocks (~> 3.12.0)
55
+ rspec-core (3.12.0)
56
+ rspec-support (~> 3.12.0)
57
+ rspec-expectations (3.12.2)
58
+ diff-lcs (>= 1.2.0, < 2.0)
59
+ rspec-support (~> 3.12.0)
60
+ rspec-mocks (3.12.3)
61
+ diff-lcs (>= 1.2.0, < 2.0)
62
+ rspec-support (~> 3.12.0)
63
+ rspec-support (3.12.0)
64
+ rubocop (1.43.0)
65
+ json (~> 2.3)
66
+ parallel (~> 1.10)
67
+ parser (>= 3.2.0.0)
68
+ rainbow (>= 2.2.2, < 4.0)
69
+ regexp_parser (>= 1.8, < 3.0)
70
+ rexml (>= 3.2.5, < 4.0)
71
+ rubocop-ast (>= 1.24.1, < 2.0)
72
+ ruby-progressbar (~> 1.7)
73
+ unicode-display_width (>= 2.4.0, < 3.0)
74
+ rubocop-ast (1.24.1)
75
+ parser (>= 3.1.1.0)
76
+ rubocop-capybara (2.17.0)
77
+ rubocop (~> 1.41)
78
+ rubocop-rake (0.6.0)
79
+ rubocop (~> 1.0)
80
+ rubocop-rspec (2.18.1)
81
+ rubocop (~> 1.33)
82
+ rubocop-capybara (~> 2.17)
83
+ ruby-progressbar (1.11.0)
84
+ rubyzip (2.3.2)
85
+ selenium-webdriver (4.9.0)
86
+ rexml (~> 3.2, >= 3.2.5)
87
+ rubyzip (>= 1.2.2, < 3.0)
88
+ websocket (~> 1.0)
89
+ unicode-display_width (2.4.2)
90
+ websocket (1.2.9)
91
+ xpath (3.2.0)
92
+ nokogiri (~> 1.8)
93
+
94
+ PLATFORMS
95
+ x86_64-linux
96
+
97
+ DEPENDENCIES
98
+ dotenv (~> 2.8, >= 2.8.1)
99
+ kindle-notebook!
100
+ pry (~> 0.14.2)
101
+ rake (~> 13.0)
102
+ rspec (~> 3.0)
103
+ rubocop (~> 1.21)
104
+ rubocop-rake
105
+ rubocop-rspec
106
+
107
+ BUNDLED WITH
108
+ 2.4.5
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Bianca Vieira
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # Kindle Notebook
2
+
3
+ Fetch your Kindle Highlights along with their context using the Selenium Webdriver
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/kindle-notebook.svg)](https://badge.fury.io/rb/kindle-notebook)
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ ```sh
12
+ $ bundle add kindle-notebook
13
+ ```
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ ```sh
18
+ $ gem install kindle-notebook
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ To get the highlights from a book:
24
+ ```rb
25
+ books = KindleNotebook::Client.books
26
+ book = books.first.fetch_highlights
27
+ book.highlights
28
+ ```
29
+
30
+ To write to a CSV file:
31
+ ```rb
32
+ book.to_csv_file # => "Docker: A Project-Based Approach to Learning - Cannon, Jason.csv"
33
+ ```
34
+
35
+ ## Examples
36
+
37
+ Book:
38
+ ```rb
39
+ #<KindleNotebook::Book:0x00007f0847c4e388
40
+ @asin="B09FJ3411G",
41
+ @author="Cannon, Jason",
42
+ @highlights=[#<KindleNotebook::Highlight:0x00007f46959d61f0 @book_asin="B09FJ3411G", ...],
43
+ @highlights_count=13,
44
+ @title="Docker: A Project-Based Approach to Learning">
45
+ ```
46
+
47
+ <!-- TODO: create highligh class -->
48
+ Highlight:
49
+ ```rb
50
+ #<KindleNotebook::Highlight:0x00007f46959d61f0
51
+ @book_asin="B09FJ3411G",
52
+ @context="If you get stuck, the logging component of systemd, called journald, can also help.",
53
+ @page="120",
54
+ @raw_context="used, for example. If you get stuck, the logging component of systemd, called journald, can also help. This journald command displays the last 20 entries in the",
55
+ @raw_text="journald,",
56
+ @text="journald">
57
+ ```
58
+
59
+ Book CSV:
60
+ ```csv
61
+ text,page,context,book_asin,raw_text,raw_context
62
+ journald,120,,B09FJ3411G,"journald,",
63
+ swarm,225,"Docker Swarm In this chapter, you're going to learn how to create and use a Docker",B09FJ3411G,swarm.,"Docker Swarm In this chapter, you're going to learn how to create and use a Docker"
64
+ ```
65
+
66
+ ## Development
67
+
68
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
69
+
70
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
71
+
72
+ ## Contributing
73
+
74
+ Bug reports and pull requests are welcome on GitHub at https://github.com/b1anca/kindle-notebook. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/b1anca/kindle-notebook/blob/master/CODE_OF_CONDUCT.md).
75
+
76
+ ## License
77
+
78
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
79
+
80
+ ## Code of Conduct
81
+
82
+ Everyone interacting in the KindleNotebook project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/b1anca/kindle-notebook/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
Binary file
Binary file
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "capybara"
4
+ require "capybara/sessionkeeper"
5
+ require "csv"
6
+ require "selenium-webdriver"
7
+
8
+ require_relative "kindle_notebook/amazon_auth"
9
+ require_relative "kindle_notebook/book"
10
+ require_relative "kindle_notebook/client"
11
+ require_relative "kindle_notebook/configuration"
12
+ require_relative "kindle_notebook/helpers"
13
+ require_relative "kindle_notebook/highlights"
14
+ require_relative "kindle_notebook/highlight"
15
+ require_relative "kindle_notebook/version"
16
+
17
+ module KindleNotebook
18
+ class Error < StandardError; end
19
+
20
+ Capybara.register_driver :chrome do |app|
21
+ options = Selenium::WebDriver::Chrome::Options.new
22
+ options.add_argument("--headless") if configuration.headless_mode
23
+ options.add_argument("--disable-gpu")
24
+ options.add_argument("--no-sandbox")
25
+
26
+ Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
27
+ end
28
+
29
+ Capybara.register_driver :firefox do |app|
30
+ options = Selenium::WebDriver::Firefox::Options.new
31
+ options.add_argument("--headless") if configuration.headless_mode
32
+
33
+ Capybara::Selenium::Driver.new(app, browser: :firefox, options: options)
34
+ end
35
+
36
+ class << self
37
+ attr_accessor :configuration
38
+
39
+ def configure
40
+ self.configuration ||= Configuration.new
41
+ yield(configuration)
42
+ end
43
+
44
+ def session
45
+ @session ||= AmazonAuth.new.sign_in
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KindleNotebook
4
+ class AmazonAuth
5
+ def initialize
6
+ @auth_session = Capybara::Session.new(KindleNotebook.configuration.selenium_driver.to_sym)
7
+ end
8
+
9
+ def sign_in
10
+ auth_session.visit(KindleNotebook.configuration.url)
11
+ return auth_session if valid_cookies?
12
+
13
+ submit_sign_in_form
14
+ auth_session.save_cookies
15
+ auth_session
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :auth_session
21
+
22
+ def valid_cookies?
23
+ auth_session.find_latest_cookie_file
24
+ auth_session.restore_cookies
25
+ auth_session.refresh
26
+ auth_session.has_current_path?("/kindle-library")
27
+ end
28
+
29
+ def submit_otp_form
30
+ print "Enter OTP: "
31
+ mfa_code = gets.chomp
32
+ auth_session.fill_in("auth-mfa-otpcode", with: mfa_code)
33
+ auth_session.first("#auth-signin-button").click
34
+ end
35
+
36
+ def submit_sign_in_form
37
+ auth_session.click_button("Sign in with your account", match: :first)
38
+ fill_in_credentials
39
+ auth_session.check("rememberMe")
40
+ auth_session.first("#signInSubmit").click
41
+ submit_otp_form if mfa?
42
+ end
43
+
44
+ def fill_in_credentials
45
+ auth_session.fill_in("ap_email", with: KindleNotebook.configuration.login)
46
+ auth_session.fill_in("ap_password", with: KindleNotebook.configuration.password)
47
+ end
48
+
49
+ def mfa?
50
+ auth_session.current_path.match?(%r{ap/mfa})
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KindleNotebook
4
+ class Book
5
+ attr_reader :author, :title, :asin
6
+ attr_accessor :highlights_count, :highlights
7
+
8
+ def initialize(book_attrs)
9
+ @author = book_attrs[:author]
10
+ @title = book_attrs[:title]
11
+ @asin = book_attrs[:asin]
12
+ @highlights_count = nil
13
+ @highlights = []
14
+ end
15
+
16
+ def fetch_highlights
17
+ open_book unless session.current_url.include?(asin)
18
+ Highlights.new(self).fetch
19
+ end
20
+
21
+ def to_csv_file
22
+ headers = highlights.first.attributes
23
+ Helpers.to_csv_file(headers, highlights, "#{title} - #{author}.csv")
24
+ end
25
+
26
+ private
27
+
28
+ def session
29
+ KindleNotebook.session
30
+ end
31
+
32
+ def open_book
33
+ session.visit(KindleNotebook.configuration.url) unless session.has_selector?("p", text: title)
34
+ session.find("p", text: title).click
35
+ sleep 3 # book might take a bit to load
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KindleNotebook
4
+ module Client
5
+ class << self
6
+ def books
7
+ @books ||= fetch_books
8
+ end
9
+
10
+ private
11
+
12
+ def fetch_books
13
+ KindleNotebook.session.find("ul#cover").all("li").map do |element|
14
+ Book.new(author: element.find("div", id: /author-/).text,
15
+ title: element.find("div", id: /title-/).text,
16
+ asin: element.find("div", match: :first)["data-asin"])
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KindleNotebook
4
+ class Configuration
5
+ attr_accessor :url, :login, :password, :selenium_driver, :headless_mode, :min_highlight_words, :max_highlight_words
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KindleNotebook
4
+ module Helpers
5
+ def self.to_csv_file(headers, list, file_name = "output.csv")
6
+ full_csv = ""
7
+ full_csv += CSV.generate_line(headers)
8
+ list.each { |element| full_csv += element.to_csv }
9
+ File.write(file_name, full_csv)
10
+ file_name
11
+ end
12
+
13
+ def self.sentence_with_word(full_text, target_word)
14
+ sentences = full_text.scan(/\s+[^.!?]*[.!?]/) # match sentences
15
+ matching_sentence = sentences.select { |s| s.include?(target_word) }.first&.strip
16
+ matching_sentence || full_text
17
+ end
18
+
19
+ def self.clean_up_text(text)
20
+ text.downcase.gsub(/[^0-9a-zA-Z -]+/, "").strip
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KindleNotebook
4
+ class Highlight
5
+ attr_reader :text, :page, :context, :book_asin, :raw_text, :raw_context
6
+
7
+ def initialize(highlight_attrs)
8
+ @text = highlight_attrs[:text]
9
+ @page = highlight_attrs[:page]
10
+ @context = highlight_attrs[:context]
11
+ @book_asin = highlight_attrs[:book_asin]
12
+ @raw_text = highlight_attrs[:raw_text]
13
+ @raw_context = highlight_attrs[:raw_context]
14
+ end
15
+
16
+ def book
17
+ @book ||= Client.books.find { |b| b.asin == book_asin }
18
+ end
19
+
20
+ def attributes
21
+ instance_variables.map { |v| v.to_s.gsub("@", "").to_sym }
22
+ end
23
+
24
+ def to_csv
25
+ CSV::Row.new(attributes, attributes.map { |a| instance_variable_get("@#{a}") }).to_s
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KindleNotebook
4
+ class Highlights
5
+ attr_accessor :book
6
+
7
+ def initialize(book)
8
+ @book = book
9
+ end
10
+
11
+ def fetch
12
+ open_notebook
13
+ add_highlights
14
+ book.highlights
15
+ end
16
+
17
+ private
18
+
19
+ def session
20
+ KindleNotebook.session
21
+ end
22
+
23
+ def add_highlights
24
+ items = notebook_highlights
25
+ close_notebook
26
+ open_search
27
+ fetch_highlights_with_context(items)
28
+ end
29
+
30
+ # fetch all highlights (without context) from notebook
31
+ def notebook_highlights
32
+ content = session.find("div", class: "notebook-content").all("ion-item")
33
+ puts "#{content.count} highlights in this book \n"
34
+ book.highlights_count = content.count
35
+ content.map { |h| parse_notebook_highlight(h) }
36
+ end
37
+
38
+ def parse_notebook_highlight(item)
39
+ { text: item.find(".notebook-editable-item-black").text,
40
+ page: item.find(".notebook-editable-item-grey").text.split(" ").last }
41
+ end
42
+
43
+ def open_notebook
44
+ show_toolbar
45
+ session.first(:xpath, '//ion-button[@item-i-d="top_menu_notebook"]', wait: 5).click
46
+ end
47
+
48
+ def close_notebook
49
+ session.find("#notebook-header-close-img").click
50
+ end
51
+
52
+ def fetch_highlights_with_context(items)
53
+ items.each_with_index do |item, index|
54
+ parsed_text = Helpers.clean_up_text(item[:text])
55
+ page = item[:page]
56
+ puts "[#{index + 1}/#{book.highlights_count}] \"#{parsed_text}\" page #{page}... "
57
+ next unless within_range?(parsed_text)
58
+
59
+ add_highlight(parsed_text, page, item[:text])
60
+ end
61
+ end
62
+
63
+ def within_range?(text)
64
+ range = KindleNotebook.configuration.min_highlight_words..KindleNotebook.configuration.max_highlight_words
65
+ range.include?(text.split(" ").count)
66
+ end
67
+
68
+ def add_highlight(text, page, raw_text)
69
+ context, raw_context = search_highlight_context(text, page)
70
+ new_highlight = Highlight.new(text: text,
71
+ page: page,
72
+ context: context,
73
+ book_asin: book.asin,
74
+ raw_text: raw_text,
75
+ raw_context: raw_context)
76
+ book.highlights.push(new_highlight)
77
+ end
78
+
79
+ def open_search
80
+ show_toolbar
81
+ session.first(:xpath, '//ion-button[@item-i-d="top_menu_search"]', wait: 2).click
82
+ end
83
+
84
+ def show_toolbar
85
+ session.first(:xpath, "//ion-header", wait: 2).hover
86
+ end
87
+
88
+ def search_highlight_context(text, page)
89
+ search_highlight(text)
90
+ context = nil
91
+ raw_context = nil
92
+ context, raw_context = context_from_element(text, page) if session.has_css?(".search-results")
93
+ clear_search
94
+ [context, raw_context]
95
+ end
96
+
97
+ def search_highlight(text)
98
+ session.find("input", class: "searchbar-input").set(text)
99
+ sleep 1 while session.has_css?(".kg-loader") # wait for search response
100
+ end
101
+
102
+ def search_results
103
+ session.find(".search-results", wait: 5).all(".search-item")
104
+ end
105
+
106
+ def context_from_element(text, page)
107
+ page_result = search_results.find { |r| r.find(".search-item-label").text.split(" ").last == page }
108
+ if page_result.nil?
109
+ puts "no context for #{text}"
110
+ return ["", ""]
111
+ end
112
+
113
+ context = page_result.find(".search-item-context").text
114
+ [Helpers.sentence_with_word(context, text), context]
115
+ end
116
+
117
+ def clear_search
118
+ session.find("input", class: "searchbar-input").set("")
119
+ sleep 1
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KindleNotebook
4
+ VERSION = "0.2.1"
5
+ end
@@ -0,0 +1,4 @@
1
+ module KindleNotebook
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kindle-notebook
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Bianca Vieira
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-05-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: capybara
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.39'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.39'
27
+ - !ruby/object:Gem::Dependency
28
+ name: capybara-sessionkeeper
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: selenium-webdriver
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.9'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dotenv
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.8'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 2.8.1
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '2.8'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 2.8.1
75
+ - !ruby/object:Gem::Dependency
76
+ name: pry
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 0.14.2
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 0.14.2
89
+ description: Fetch your Kindle Highlights along with their context using the Selenium
90
+ Webdriver
91
+ email:
92
+ - bncvr@outlook.com
93
+ executables: []
94
+ extensions: []
95
+ extra_rdoc_files: []
96
+ files:
97
+ - ".env_sample"
98
+ - ".rspec"
99
+ - ".rubocop.yml"
100
+ - CODE_OF_CONDUCT.md
101
+ - Gemfile
102
+ - Gemfile.lock
103
+ - LICENSE.txt
104
+ - README.md
105
+ - Rakefile
106
+ - kindle_notebook-0.2.0.gem
107
+ - kindle_notebook-0.2.1.gem
108
+ - lib/kindle-notebook.rb
109
+ - lib/kindle_notebook/amazon_auth.rb
110
+ - lib/kindle_notebook/book.rb
111
+ - lib/kindle_notebook/client.rb
112
+ - lib/kindle_notebook/configuration.rb
113
+ - lib/kindle_notebook/helpers.rb
114
+ - lib/kindle_notebook/highlight.rb
115
+ - lib/kindle_notebook/highlights.rb
116
+ - lib/kindle_notebook/version.rb
117
+ - sig/kindle_notebook.rbs
118
+ homepage: https://github.com/b1anca/kindle-notebook
119
+ licenses:
120
+ - MIT
121
+ metadata:
122
+ homepage_uri: https://github.com/b1anca/kindle-notebook
123
+ source_code_uri: https://github.com/b1anca/kindle-notebook
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: 2.6.0
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubygems_version: 3.4.1
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: Kindle Notebook
143
+ test_files: []