bookmeter_exporter 0.1.0

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: 6bdd15cf3580a2b9112053f97c67b50388f8d0c6341a0f73f77f81665fca568d
4
+ data.tar.gz: 906480de373aacdf3ab810151cdf7887b2cabcf3969fa4082f7213c36e282891
5
+ SHA512:
6
+ metadata.gz: cdf26fd81c41dc05cf5875515f0422332b43e249f5bf89c6b099a22e1b1af346e7574b865ac3f8d6ef42a9476434ae25d5055ba90b191a9c8fea4070c6e924ca
7
+ data.tar.gz: 11f56ecd657fff8879b6299012125bf9f0e64eb9d61d09f425f38217a182f018ee1658bf6d2cc49f894332d44c7bd035758366f302e7209153c68f6c09242a8c
@@ -0,0 +1,19 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ ruby: [2.6, 2.7, 3.0]
11
+ steps:
12
+ - uses: actions/checkout@v2
13
+ - name: Set up Ruby
14
+ uses: ruby/setup-ruby@v1
15
+ with:
16
+ ruby-version: ${{ matrix.ruby}}
17
+ bundler-cache: true
18
+ - name: Run the default task
19
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ books.csv
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,23 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+ NewCops: enable
4
+
5
+ Metrics/AbcSize:
6
+ Max: 30
7
+
8
+ Metrics/MethodLength:
9
+ Max: 20
10
+
11
+ Style/Documentation:
12
+ Enabled: false
13
+
14
+ Style/StringLiterals:
15
+ Enabled: true
16
+ EnforcedStyle: double_quotes
17
+
18
+ Style/StringLiteralsInInterpolation:
19
+ Enabled: true
20
+ EnforcedStyle: double_quotes
21
+
22
+ Layout/LineLength:
23
+ Max: 120
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.0.2
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2021-07-24
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem "rake", "~> 13.0"
8
+
9
+ gem "rspec", "~> 3.0"
10
+
11
+ gem "rubocop", "~> 1.7"
12
+ gem "rubocop-rake"
13
+ gem "rubocop-rspec"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Ikuo Degawa
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
+ # bookmeter_exporter
2
+
3
+ A command line tool to export read books data from https://bookmeter.com as CSV
4
+
5
+ [読書メーター](https://bookmeter.com)から読み終わった本をエクスポートするCLIツール
6
+
7
+ ## Requirements
8
+
9
+ * Ruby >= 2.6 and bundler
10
+ * `chromedriver` in $PATH and same version of Google Chrome
11
+ * Download: https://chromedriver.chromium.org/downloads
12
+ * 読書メーターのアカウントにメールアドレスとパスワードが設定されていること
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ $ gem install bookmeter_exporter
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```bash
23
+ $ bookmeter_exporter
24
+ Commands:
25
+ bookmeter_exporter export EMAIL # This command exports all read books of an account as CSV.
26
+ bookmeter_exporter help [COMMAND] # Describe available commands or one specific command
27
+ bookmeter_exporter version # Display version info
28
+ ```
29
+
30
+ Example:
31
+
32
+ ```bash
33
+ $ bookmeter_exporter export ikuwow@example.com
34
+ Password for EMAIL: # Type the password of your account
35
+ Starting Chrome... # Chrome window opens
36
+ Login success.
37
+ Book count: XXX
38
+ XX books fetched...
39
+ YY books fetched...
40
+ [...]
41
+ Books are successfully exported as './books.csv'.
42
+ ```
43
+
44
+ ## CSV Format
45
+
46
+ |ASIN|Read Date|Review|
47
+ |---|---|---|
48
+ |ex. B071K5WM6P|ex. 2021/07/31|ex. This book is awesome!|
49
+
50
+ Ex.
51
+
52
+ ```csv
53
+ 4408167967,2021/06/02,ここまで読んだ
54
+ B09366V8V2,2021/05/13,""
55
+ B093KCX58C,2021/05/12,""
56
+ ```
57
+
58
+ ## Miscellaneous
59
+
60
+ ### [ブクログ](https://booklog.jp)へのインポート
61
+
62
+ ブクログはCSVファイルから本をインポートすることが出来ます:
63
+ https://booklog.zendesk.com/hc/ja/articles/360048930533-他の読書管理サイトからブクログへデータを移行したいです
64
+
65
+ bookmeter_exporterで出力したCSVをExcel等でフォーマットを整えるとよいです。
66
+
67
+ ## Development
68
+
69
+ 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.
70
+
71
+ ## Contributing
72
+
73
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ikuwow/bookmeter_exporter.
74
+
75
+ ## Wishlist
76
+
77
+ * More tests
78
+ * More fields in CSV
79
+
80
+ ## License
81
+
82
+ MIT
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]
data/_config.yml ADDED
@@ -0,0 +1 @@
1
+ theme: jekyll-theme-cayman
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "bookmeter_exporter"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/bookmeter_exporter/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "bookmeter_exporter"
7
+ spec.version = BookmeterExporter::VERSION
8
+ spec.authors = ["Ikuo Degawa"]
9
+ spec.email = ["degawa@ikuwow.com"]
10
+
11
+ spec.summary = "Export user history of bookmeter.com"
12
+ spec.homepage = "https://ikuwow.github.io/bookmeter_exporter/"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 2.6"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/ikuwow/bookmeter_exporter"
18
+ spec.metadata["changelog_uri"] = "https://github.com/ikuwow/bookmeter_exporter/blob/master/CHANGELOG.md"
19
+
20
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
22
+ end
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_dependency "selenium-webdriver", "~> 3.142.2"
28
+ spec.add_dependency "thor", "~> 1.1.0"
29
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require "bookmeter_exporter"
6
+
7
+ BookmeterExporter::CLI.start(ARGV)
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "bookmeter_exporter/version"
4
+ require_relative "bookmeter_exporter/cli"
5
+
6
+ module BookmeterExporter
7
+ class Error < StandardError; end
8
+ # Your code goes here...
9
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module BookmeterExporter
6
+ Book = Struct.new(:asin, :read_date, :review)
7
+
8
+ class Books
9
+ extend Forwardable
10
+
11
+ def_delegator :@books, :[]
12
+ def_delegator :@books, :<<
13
+
14
+ def initialize
15
+ @books = []
16
+ end
17
+
18
+ def to_csv
19
+ CSV.generate do |csv|
20
+ @books.each do |book|
21
+ csv << [book.asin, book.read_date, book.review]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require "csv"
5
+ require_relative "crawler"
6
+ require_relative "version"
7
+
8
+ module BookmeterExporter
9
+ class CLI < Thor
10
+ class << self
11
+ def exit_on_failure?
12
+ true
13
+ end
14
+ end
15
+
16
+ desc "export EMAIL", "This command exports all read books of an account as CSV."
17
+ option :destination, aliases: :d, desc: "path where CSV file is saved to"
18
+ def export(email)
19
+ password = ask("Password for #{email}:", echo: false)
20
+ puts ""
21
+
22
+ crawler = BookmeterExporter::Crawler.new(email, password)
23
+ books = crawler.crawl
24
+
25
+ destination = options[:destination] || "./books.csv"
26
+ IO.write(destination, books.to_csv)
27
+ puts "Books are successfully exported as '#{destination}'."
28
+ end
29
+
30
+ desc "version", "Display version info"
31
+ def version
32
+ puts VERSION
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "selenium-webdriver"
4
+ require "uri"
5
+ require "cgi"
6
+
7
+ require_relative "books"
8
+
9
+ module BookmeterExporter
10
+ class Crawler
11
+ def initialize(email, password)
12
+ @email = email
13
+ @password = password
14
+ @root_url = URI("https://bookmeter.com/")
15
+ @user_id = nil
16
+ end
17
+
18
+ def crawl
19
+ start_webdriver
20
+ login
21
+ book_urls = collect_book_urls
22
+ fetch_books(book_urls)
23
+ end
24
+
25
+ private
26
+
27
+ def start_webdriver
28
+ puts "Starting Chrome..."
29
+ @driver = Selenium::WebDriver.for :chrome
30
+ @wait = Selenium::WebDriver::Wait.new(timeout: 10)
31
+ end
32
+
33
+ def login
34
+ @driver.get @root_url.merge("/login")
35
+ @driver.find_element(:id, "session_email_address").send_keys @email
36
+ @driver.find_element(:id, "session_password").send_keys @password
37
+ @driver.find_element(:css, "form[action='/login'] button[type=submit]").click
38
+ @wait.until { @driver.current_url == @root_url.merge("/home").to_s }
39
+
40
+ puts "Login success."
41
+
42
+ @user_id = @driver.find_element(:css, ".personal-account__data__link").attribute("href")[%r{/([0-9]+)$}, 1]
43
+ end
44
+
45
+ def fetch_books(book_urls)
46
+ books = Books.new
47
+ fetched_books_count = 0
48
+ book_urls.each do |url|
49
+ books << fetch_book(url)
50
+ fetched_books_count += 1
51
+ puts "#{fetched_books_count} books fetched..." if (fetched_books_count % 10).zero?
52
+ end
53
+ puts "All books fetched."
54
+ books
55
+ end
56
+
57
+ def collect_book_urls
58
+ go_read_books
59
+
60
+ last_page_url = @driver.find_element(:css, "ul.bm-pagination").find_element(:link_text, "最後").attribute("href")
61
+ last_page = CGI.parse(URI.parse(last_page_url).query)["page"].shift.to_i
62
+
63
+ urls = []
64
+ (1..last_page).each do |page|
65
+ page_url = @root_url.merge("/users/#{@user_id}/books/read?page=#{page}")
66
+ @driver.get page_url
67
+
68
+ book_list_css_selector = ".book-list--grid ul.book-list__group"
69
+ @wait.until { @driver.current_url == page_url.to_s }
70
+ @driver.find_elements(:css, book_list_css_selector).each do |ul|
71
+ ul.find_elements(:css, "li .detail__title").each do |li|
72
+ urls << li.find_element(:tag_name, "a").attribute("href")
73
+ end
74
+ end
75
+ end
76
+
77
+ puts "Book count: #{urls.count}"
78
+ urls
79
+ end
80
+
81
+ def go_read_books
82
+ read_books_url = @root_url.merge("/users/#{@user_id}/books/read")
83
+ @driver.get read_books_url
84
+ @wait.until { sleep 3 }
85
+ end
86
+
87
+ def fetch_book(url)
88
+ @driver.get url
89
+ @wait.until do
90
+ %r{/books/[0-9]+$}.match(@driver.current_url)
91
+ end
92
+
93
+ book_asin = @driver.find_element(:css, ".sidebar__group .group__image a").attribute("href")
94
+ .gsub(%r{https://www.amazon.co.jp/dp/product/(.+)/.*}, '\1')
95
+ read_date = @driver.find_element(:css, ".read-book__date").text
96
+ review_text = @driver.find_element(:css, ".read-book__content").text
97
+
98
+ Book.new(book_asin, read_date, review_text)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BookmeterExporter
4
+ VERSION = "0.1.0"
5
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bookmeter_exporter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ikuo Degawa
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-07-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: selenium-webdriver
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.142.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.142.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.1.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.1.0
41
+ description:
42
+ email:
43
+ - degawa@ikuwow.com
44
+ executables:
45
+ - bookmeter_exporter
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".github/workflows/main.yml"
50
+ - ".gitignore"
51
+ - ".rspec"
52
+ - ".rubocop.yml"
53
+ - ".ruby-version"
54
+ - CHANGELOG.md
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - _config.yml
60
+ - bin/console
61
+ - bin/setup
62
+ - bookmeter_exporter.gemspec
63
+ - exe/bookmeter_exporter
64
+ - lib/bookmeter_exporter.rb
65
+ - lib/bookmeter_exporter/books.rb
66
+ - lib/bookmeter_exporter/cli.rb
67
+ - lib/bookmeter_exporter/crawler.rb
68
+ - lib/bookmeter_exporter/version.rb
69
+ homepage: https://ikuwow.github.io/bookmeter_exporter/
70
+ licenses:
71
+ - MIT
72
+ metadata:
73
+ homepage_uri: https://ikuwow.github.io/bookmeter_exporter/
74
+ source_code_uri: https://github.com/ikuwow/bookmeter_exporter
75
+ changelog_uri: https://github.com/ikuwow/bookmeter_exporter/blob/master/CHANGELOG.md
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '2.6'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubygems_version: 3.2.25
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Export user history of bookmeter.com
95
+ test_files: []