kindle_util 0.5.0
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.
- data/.gitignore +17 -0
- data/CHANGELOG +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +46 -0
- data/Rakefile +1 -0
- data/bin/kindle_util +5 -0
- data/kindle_util.gemspec +24 -0
- data/lib/kindle_util.rb +6 -0
- data/lib/kindle_util/amazon_crawler.rb +91 -0
- data/lib/kindle_util/cli.rb +80 -0
- data/lib/kindle_util/version.rb +3 -0
- metadata +124 -0
data/.gitignore
ADDED
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Matt Conway
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# kindle_util
|
2
|
+
|
3
|
+
A utility for performing bulk actions against your kindle library, most notably to allow resetting the "last page read" for all your kindle books.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
$ gem install kindle_util
|
8
|
+
|
9
|
+
And then to reset "last page read" for all books, execute:
|
10
|
+
|
11
|
+
$ kindle_util -a reset_lpr
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
$ kindle_util -h
|
16
|
+
Usage:
|
17
|
+
kindle_util [OPTIONS] [FILTER] ...
|
18
|
+
|
19
|
+
Parameters:
|
20
|
+
[FILTER] ... The filters to limit the items acted upon.
|
21
|
+
These should be given as 'field_name=value',
|
22
|
+
where value is treated as a regex. Perform
|
23
|
+
the list action with --debug to see all
|
24
|
+
possible fields
|
25
|
+
|
26
|
+
Options:
|
27
|
+
-u, --username USERNAME Your amazon username/email
|
28
|
+
-p, --password PASSWORD Your amazon password
|
29
|
+
-a, --action ACTION The action to perform on all the selected
|
30
|
+
books, where action is one of:
|
31
|
+
list: Display the selected items
|
32
|
+
reset_lpr: Reset the last page read marker
|
33
|
+
(default: "list")
|
34
|
+
-c, --[no-]cache Cache (or not) the full list of purchased
|
35
|
+
items (default: true)
|
36
|
+
-d, --debug More verbose logging
|
37
|
+
-v, --version Show version and exit
|
38
|
+
-h, --help print help
|
39
|
+
|
40
|
+
## Contributing
|
41
|
+
|
42
|
+
1. Fork it
|
43
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
44
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
45
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
46
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/kindle_util
ADDED
data/kindle_util.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'kindle_util/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "kindle_util"
|
8
|
+
gem.version = KindleUtil::VERSION
|
9
|
+
gem.authors = ["Matt Conway"]
|
10
|
+
gem.email = ["matt@conwaysplace.com"]
|
11
|
+
gem.description = %q{A utility for performing bulk actions against your kindle library, most notably to allow resetting the "last page read" for all your kindle books.}
|
12
|
+
gem.summary = %q{A utility for performing bulk actions against your kindle library}
|
13
|
+
gem.homepage = "http://github.com/wr0ngway/kindle_util"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency("logging")
|
21
|
+
gem.add_dependency("highline")
|
22
|
+
gem.add_dependency("clamp")
|
23
|
+
gem.add_dependency("mechanize")
|
24
|
+
end
|
data/lib/kindle_util.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'mechanize'
|
3
|
+
|
4
|
+
module KindleUtil
|
5
|
+
class AmazonCrawler
|
6
|
+
attr_reader :owned_items
|
7
|
+
|
8
|
+
def initialize(user, pass, cache)
|
9
|
+
@user = user
|
10
|
+
@pass = pass
|
11
|
+
@cache_file = File.expand_path("~/.kindle_util.json")
|
12
|
+
|
13
|
+
@agent = Mechanize.new do |agent|
|
14
|
+
agent.user_agent_alias = 'Mac Safari'
|
15
|
+
agent.follow_meta_refresh = true
|
16
|
+
agent.redirect_ok = true
|
17
|
+
end
|
18
|
+
|
19
|
+
@owned_items = JSON.parse(File.read(@cache_file)) rescue []
|
20
|
+
if @owned_items.size == 0 || ! cache
|
21
|
+
@owned_items = fetch_ownership
|
22
|
+
File.write(@cache_file, @owned_items.to_json)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def login
|
27
|
+
@manage_kindle_page ||= begin
|
28
|
+
logger.debug "Logging into amazon web ui"
|
29
|
+
home_page = @agent.get("http://www.amazon.com")
|
30
|
+
logger.debug "Got home page: #{home_page.title.strip}"
|
31
|
+
signin_page = home_page.link_with(:text => "Manage Your Kindle").click
|
32
|
+
logger.debug "Got sign in page: #{signin_page.title.strip}"
|
33
|
+
form = signin_page.forms.first
|
34
|
+
form.email = @user
|
35
|
+
form['ap_signin_existing_radio'] = "1"
|
36
|
+
form.password = @pass
|
37
|
+
manage_page = form.submit
|
38
|
+
logger.debug "Got manage page: #{manage_page.title.strip}"
|
39
|
+
manage_page
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def unescape(data)
|
44
|
+
case data
|
45
|
+
when Array then data.collect {|d| unescape(d) }
|
46
|
+
when Hash then Hash[data.collect {|k, v| [unescape(k), unescape(v)] }]
|
47
|
+
when String then CGI.unescapeHTML(data)
|
48
|
+
else data
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch_ownership()
|
53
|
+
login
|
54
|
+
owned_items = []
|
55
|
+
ownership_url = "https://www.amazon.com/gp/digital/fiona/manage/features/order-history/ajax/queryOwnership_refactored.html"
|
56
|
+
|
57
|
+
offset = 0
|
58
|
+
count = 100
|
59
|
+
has_more = true
|
60
|
+
while has_more do
|
61
|
+
logger.debug "Fetching ownership data offset=#{offset}, count=#{count}"
|
62
|
+
ownership_data = @agent.post(ownership_url, "contentType" => "all",
|
63
|
+
"randomizer" => rand(10000000000000),
|
64
|
+
"count" => count,
|
65
|
+
"offset" => offset)
|
66
|
+
data = JSON.parse(ownership_data.body)
|
67
|
+
items = data['data']['items']
|
68
|
+
items = unescape(items)
|
69
|
+
owned_items.concat(items)
|
70
|
+
|
71
|
+
has_more = data['data']['hasMore'].to_i != 0
|
72
|
+
offset += count
|
73
|
+
end
|
74
|
+
logger.debug "Got data for #{owned_items.size} books"
|
75
|
+
return owned_items
|
76
|
+
end
|
77
|
+
|
78
|
+
def reset_lpr(item)
|
79
|
+
login
|
80
|
+
asin = item['asin']
|
81
|
+
sid = @agent.cookies.find {|c| c.name == "session-id" }.value
|
82
|
+
reset_lpr_url = "https://www.amazon.com/gp/digital/fiona/du/reset-lpr.html/ref=kinw_myk_lpr"
|
83
|
+
logger.debug "Resetting last page read: asin=#{asin}, sid=#{sid}"
|
84
|
+
response = @agent.post(reset_lpr_url, "asin" => asin, "sid" => sid)
|
85
|
+
data = JSON.parse(response.body.gsub("'", '"'))
|
86
|
+
logger.error "Failed to reset last page read for asin=#{asin}: #{data["error"]}" if data["error"]
|
87
|
+
data['data'].to_i == 1
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'clamp'
|
2
|
+
require 'highline/import'
|
3
|
+
|
4
|
+
module KindleUtil
|
5
|
+
class CLI < Clamp::Command
|
6
|
+
|
7
|
+
ACTIONS = {
|
8
|
+
"list" => "Display the selected items",
|
9
|
+
"reset_lpr" => "Reset the last page read marker"
|
10
|
+
}
|
11
|
+
|
12
|
+
option ["-u", "--username"], "USERNAME", "Your amazon username/email"
|
13
|
+
option ["-p", "--password"], "PASSWORD", "Your amazon password"
|
14
|
+
option ["-a", "--action"], "ACTION", "The action to perform on all the selected\n" +
|
15
|
+
"books, where action is one of:" +
|
16
|
+
ACTIONS.collect {|k, v| "\n #{k}: #{v}"}.join("") +
|
17
|
+
"\n ", :default => "list" do |action|
|
18
|
+
if ACTIONS[action].nil?
|
19
|
+
msg = "Invalid action: '#{action}'"
|
20
|
+
msg += "\nAction must be one of:"
|
21
|
+
ACTIONS.each do |k, v|
|
22
|
+
msg += "\n\t#{k}: #{v}"
|
23
|
+
end
|
24
|
+
raise ArgumentError, msg
|
25
|
+
end
|
26
|
+
action
|
27
|
+
end
|
28
|
+
option ["-c", "--[no-]cache"], :flag, "Cache (or not) the full list of purchased\nitems", :default => true
|
29
|
+
option ["-d", "--debug"], :flag, "More verbose logging"
|
30
|
+
option ["-v", "--version"], :flag, "Show version and exit" do
|
31
|
+
puts "kindle_util v#{KindleUtil::VERSION}"
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
|
35
|
+
parameter "[FILTER] ...", "The filters to limit the items acted upon.\n" +
|
36
|
+
"These should be given as 'field_name=value',\n" +
|
37
|
+
"where value is treated as a regex. Perform\n" +
|
38
|
+
"the list action with --debug to see all\n" +
|
39
|
+
"possible fields"
|
40
|
+
|
41
|
+
def execute
|
42
|
+
Logging.logger.root.appenders = Logging.appenders.stdout
|
43
|
+
Logging.logger.root.level = debug? ? :debug : :info
|
44
|
+
|
45
|
+
self.username ||= ask("Enter amazon username: ")
|
46
|
+
self.password ||= ask("Enter amazon password: ") { |q| q.echo = false }
|
47
|
+
|
48
|
+
crawler = AmazonCrawler.new(username, password, cache?)
|
49
|
+
|
50
|
+
items = crawler.owned_items
|
51
|
+
filter_list.each do |name, pattern|
|
52
|
+
items = items.select {|item| item[name] =~ pattern }
|
53
|
+
end
|
54
|
+
|
55
|
+
case action
|
56
|
+
when "list"
|
57
|
+
items.each do |item|
|
58
|
+
puts debug? ? item.pretty_inspect : format_item(item)
|
59
|
+
end
|
60
|
+
when "reset_lpr"
|
61
|
+
items.each do |item|
|
62
|
+
logger.info "Resetting last page read for: #{format_item(item)}"
|
63
|
+
crawler.reset_lpr(item)
|
64
|
+
end
|
65
|
+
else
|
66
|
+
logger.error "Unknown action: #{action}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def filter_list=(filters)
|
71
|
+
@filter_list = filters.collect {|f| f.split('=') }.collect {|k,v| [k, /#{v}/]}
|
72
|
+
end
|
73
|
+
|
74
|
+
def format_item(item)
|
75
|
+
"[#{item['asin']}] #{item['title']} (#{item['author']})"
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kindle_util
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matt Conway
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: logging
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: highline
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: clamp
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: mechanize
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: A utility for performing bulk actions against your kindle library, most
|
79
|
+
notably to allow resetting the "last page read" for all your kindle books.
|
80
|
+
email:
|
81
|
+
- matt@conwaysplace.com
|
82
|
+
executables:
|
83
|
+
- kindle_util
|
84
|
+
extensions: []
|
85
|
+
extra_rdoc_files: []
|
86
|
+
files:
|
87
|
+
- .gitignore
|
88
|
+
- CHANGELOG
|
89
|
+
- Gemfile
|
90
|
+
- LICENSE.txt
|
91
|
+
- README.md
|
92
|
+
- Rakefile
|
93
|
+
- bin/kindle_util
|
94
|
+
- kindle_util.gemspec
|
95
|
+
- lib/kindle_util.rb
|
96
|
+
- lib/kindle_util/amazon_crawler.rb
|
97
|
+
- lib/kindle_util/cli.rb
|
98
|
+
- lib/kindle_util/version.rb
|
99
|
+
homepage: http://github.com/wr0ngway/kindle_util
|
100
|
+
licenses: []
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ! '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 1.8.24
|
120
|
+
signing_key:
|
121
|
+
specification_version: 3
|
122
|
+
summary: A utility for performing bulk actions against your kindle library
|
123
|
+
test_files: []
|
124
|
+
has_rdoc:
|