readwise 0.4.0 → 1.0.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.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -1
- data/CHANGELOG.md +15 -3
- data/CODE_OF_CONDUCT.md +1 -1
- data/README.md +111 -4
- data/lib/readwise/book.rb +1 -0
- data/lib/readwise/client.rb +247 -23
- data/lib/readwise/document.rb +140 -0
- data/lib/readwise/highlight.rb +40 -0
- data/lib/readwise/review.rb +17 -0
- data/lib/readwise/tag.rb +5 -1
- data/lib/readwise/version.rb +1 -1
- data/lib/readwise.rb +2 -2
- data/readwise.gemspec +2 -1
- metadata +10 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3d14c72c58cb11dfcb79c0b3b232abb8b997dc230493d5e0765f186999accba
|
4
|
+
data.tar.gz: 037ac6d22c2d195607023e99eebbd71f2885996dd40e16d49f33bc403549ebd9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b25268acdd085ba3252f828a0f85051f492de64fd851e96a5a0e38bfcfb6e25070df8bc38d7fa94aa52c1433003d6dc8d57ca501d7261bef010e510b6711c13
|
7
|
+
data.tar.gz: 1d1c7e40a53214750405a866832427aadce2ad3dc432a4d9e4ab0610520400b1f5fb14d46d90712cc0219621eb2bef21c4bff40bb62def052e99409bfd0b0b86
|
data/.github/FUNDING.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -2,17 +2,29 @@
|
|
2
2
|
|
3
3
|
## [Unreleased]
|
4
4
|
|
5
|
+
## [1.0.0] - 2025-06-29
|
6
|
+
- Add Readwise API v3 support for Reader documents ([#13](https://github.com/joshbeckman/readwise-ruby/pull/13))
|
7
|
+
- Add `daily_review` client method and Review type ([#14](https://github.com/joshbeckman/readwise-ruby/pull/14))
|
8
|
+
- Add comprehensive document API methods (list, get, create, update, delete)
|
9
|
+
- Add support for document tags and metadata handling
|
10
|
+
- Add tests for document API functionality
|
11
|
+
|
12
|
+
## [0.5.0] - 2023-06-15
|
13
|
+
- Add methods to create/update highlights ([#8](https://github.com/joshbeckman/readwise-ruby/pull/12))
|
14
|
+
- Add methods to add/update/remove highlight tags
|
15
|
+
- Add method to get book
|
16
|
+
|
5
17
|
## [0.4.0] - 2023-03-19
|
6
18
|
|
7
|
-
- Add `get_highlight` client method ([#10](https://github.com/
|
19
|
+
- Add `get_highlight` client method ([#10](https://github.com/joshbeckman/readwise-ruby/pull/10) from [@ajistrying](https://github.com/ajistrying))
|
8
20
|
|
9
21
|
## [0.3.0] - 2023-02-28
|
10
22
|
|
11
|
-
- Change `Tag` from hash to struct ([#6](https://github.com/
|
23
|
+
- Change `Tag` from hash to struct ([#6](https://github.com/joshbeckman/readwise-ruby/pull/6) from [@ajistrying](https://github.com/ajistrying))
|
12
24
|
|
13
25
|
## [0.2.0] - 2023-02-20
|
14
26
|
|
15
|
-
- Add support for `Book.note` ([#4](https://github.com/
|
27
|
+
- Add support for `Book.note` ([#4](https://github.com/joshbeckman/readwise-ruby/pull/4))
|
16
28
|
|
17
29
|
## [0.1.0] - 2023-01-16
|
18
30
|
|
data/CODE_OF_CONDUCT.md
CHANGED
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
|
|
55
55
|
## Enforcement
|
56
56
|
|
57
57
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
-
reported by contacting the project team at
|
58
|
+
reported by contacting the project team at josh@joshbeckman.org. All
|
59
59
|
complaints will be reviewed and investigated and will result in a response that
|
60
60
|
is deemed necessary and appropriate to the circumstances. The project team is
|
61
61
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Readwise
|
2
2
|
|
3
|
-
[
|
3
|
+
[](https://badge.fury.io/rb/readwise) [](https://github.com/joshbeckman/readwise-ruby/actions/workflows/ruby.yml)
|
4
|
+
|
5
|
+
[Readwise](https://readwise.io/) is an application suite to store, revisit, and learn from your book and article highlights. This is a basic library to call the [Readwise API](https://readwise.io/api_deets) to read and write highlights, and manage Reader documents through the [Reader API](https://readwise.io/reader_api).
|
4
6
|
|
5
7
|
This library is not at 100% coverage of the API, so if you need a method that is missing, open an issue or contribute changes!
|
6
8
|
|
@@ -24,6 +26,10 @@ Or install it yourself as:
|
|
24
26
|
|
25
27
|
First, obtain an API access token from https://readwise.io/access_token.
|
26
28
|
|
29
|
+
### Highlights API (V2)
|
30
|
+
|
31
|
+
The [V2 API](https://readwise.io/api_deets) provides access to your highlights and books:
|
32
|
+
|
27
33
|
```ruby
|
28
34
|
client = Readwise::Client.new(token: token)
|
29
35
|
|
@@ -33,17 +39,118 @@ books = client.export(book_ids: ['123']) # export specific highlights
|
|
33
39
|
|
34
40
|
puts books.first.title # books are Readwise::Book structs
|
35
41
|
puts books.first.highlights.map(&:text) # highlights are Readwise::Highlight structs
|
42
|
+
|
43
|
+
# create a highlight
|
44
|
+
create = Readwise::HighlightCreate.new(text: 'foobar', author: 'Joan')
|
45
|
+
highlight = client.create_highlight(highlight: create)
|
46
|
+
|
47
|
+
# update a highlight
|
48
|
+
update = Readwise::HighlightUpdate.new(text: 'foobaz', color: 'yellow')
|
49
|
+
updated = client.update_highlight(highlight: highlight, update: update)
|
50
|
+
|
51
|
+
# add a tag to a highlight
|
52
|
+
tag = Readwise::Tag.new(name: 'foobar')
|
53
|
+
added_tag = client.add_highlight_tag(highlight: highlight, tag: tag)
|
54
|
+
|
55
|
+
# update a tag on a highlight
|
56
|
+
added_tag.name = 'bing'
|
57
|
+
updated_tag = client.update_highlight_tag(highlight: highlight, tag: added_tag)
|
58
|
+
|
59
|
+
# remove a tag from a highlight
|
60
|
+
client.remove_highlight_tag(highlight: highlight, tag: added_tag)
|
61
|
+
|
62
|
+
# get daily review highlights
|
63
|
+
daily_review = client.daily_review
|
64
|
+
puts daily_review.id
|
65
|
+
puts daily_review.url
|
66
|
+
puts daily_review.completed?
|
67
|
+
puts daily_review.highlights.size
|
68
|
+
puts daily_review.highlights.first.text
|
69
|
+
```
|
70
|
+
|
71
|
+
### Reader API (V3)
|
72
|
+
|
73
|
+
The [V3 API](https://readwise.io/reader_api) provides access to Readwise Reader functionality for managing documents (articles, PDFs, etc.):
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
# Get all documents
|
77
|
+
documents = client.get_documents
|
78
|
+
|
79
|
+
# Get documents with filters
|
80
|
+
documents = client.get_documents(
|
81
|
+
updated_after: '2023-01-01T00:00:00Z',
|
82
|
+
location: 'new', # 'new', 'later', 'archive', or 'feed'
|
83
|
+
category: 'article' # 'article', 'email', 'rss', 'highlight', 'note', 'pdf', 'epub', 'tweet', 'video'
|
84
|
+
)
|
85
|
+
|
86
|
+
# Get a specific document
|
87
|
+
document = client.get_document(document_id: '123456')
|
88
|
+
|
89
|
+
puts document.title
|
90
|
+
puts document.author
|
91
|
+
puts document.url
|
92
|
+
puts document.reading_progress
|
93
|
+
puts document.location
|
94
|
+
puts document.category
|
95
|
+
|
96
|
+
# Check document properties
|
97
|
+
puts document.read? # reading progress >= 85%
|
98
|
+
puts document.read?(threshold: 0.5) # custom threshold
|
99
|
+
puts document.parent? # is this a top-level document?
|
100
|
+
puts document.child? # is this a highlight/note of another document?
|
101
|
+
|
102
|
+
# Check location
|
103
|
+
puts document.in_new?
|
104
|
+
puts document.in_later?
|
105
|
+
puts document.in_archive?
|
106
|
+
|
107
|
+
# Check category
|
108
|
+
puts document.article?
|
109
|
+
puts document.pdf?
|
110
|
+
puts document.epub?
|
111
|
+
puts document.tweet?
|
112
|
+
puts document.video?
|
113
|
+
puts document.book?
|
114
|
+
puts document.email?
|
115
|
+
puts document.rss?
|
116
|
+
puts document.highlight?
|
117
|
+
puts document.note?
|
118
|
+
|
119
|
+
# Access timestamps
|
120
|
+
puts document.created_at_time
|
121
|
+
puts document.updated_at_time
|
122
|
+
puts document.published_date_time
|
123
|
+
|
124
|
+
# Create a new document
|
125
|
+
document_create = Readwise::DocumentCreate.new(
|
126
|
+
url: 'https://example.com/article',
|
127
|
+
title: 'My Article',
|
128
|
+
author: 'John Doe',
|
129
|
+
html: '<p>Article content</p>',
|
130
|
+
summary: 'A brief summary',
|
131
|
+
location: 'new', # 'new', 'later', 'archive', or 'feed'
|
132
|
+
category: 'article', # 'article', 'email', 'rss', etc.
|
133
|
+
tags: ['technology', 'programming'],
|
134
|
+
notes: 'My personal notes',
|
135
|
+
should_clean_html: true,
|
136
|
+
saved_using: 'api'
|
137
|
+
)
|
138
|
+
|
139
|
+
document = client.create_document(document: document_create)
|
140
|
+
|
141
|
+
# Create multiple documents
|
142
|
+
documents = client.create_documents(documents: [document_create1, document_create2])
|
36
143
|
```
|
37
144
|
|
38
145
|
## Development
|
39
146
|
|
40
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec
|
147
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
41
148
|
|
42
149
|
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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
43
150
|
|
44
151
|
## Contributing
|
45
152
|
|
46
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
153
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/joshbeckman/readwise-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
47
154
|
|
48
155
|
## License
|
49
156
|
|
@@ -51,4 +158,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
51
158
|
|
52
159
|
## Code of Conduct
|
53
160
|
|
54
|
-
Everyone interacting in the Readwise project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
161
|
+
Everyone interacting in the Readwise project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/joshbeckman/readwise-ruby/blob/main/CODE_OF_CONDUCT.md).
|
data/lib/readwise/book.rb
CHANGED
data/lib/readwise/client.rb
CHANGED
@@ -3,34 +3,137 @@ require 'net/http'
|
|
3
3
|
require_relative 'book'
|
4
4
|
require_relative 'highlight'
|
5
5
|
require_relative 'tag'
|
6
|
+
require_relative 'document'
|
7
|
+
require_relative 'review'
|
6
8
|
|
7
9
|
module Readwise
|
8
10
|
class Client
|
9
11
|
class Error < StandardError; end
|
10
12
|
|
11
13
|
BASE_URL = "https://readwise.io/api/v2/"
|
14
|
+
V3_BASE_URL = "https://readwise.io/api/v3/"
|
12
15
|
|
13
16
|
def initialize(token: nil)
|
14
17
|
raise ArgumentError unless token
|
18
|
+
|
15
19
|
@token = token.to_s
|
16
20
|
end
|
17
21
|
|
22
|
+
def create_document(document:)
|
23
|
+
raise ArgumentError unless document.is_a?(Readwise::DocumentCreate)
|
24
|
+
|
25
|
+
url = V3_BASE_URL + 'save/'
|
26
|
+
|
27
|
+
res = post_readwise_request(url, payload: document.serialize)
|
28
|
+
get_document(document_id: res['id'])
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_documents(documents: [])
|
32
|
+
return [] unless documents.any?
|
33
|
+
|
34
|
+
documents.map do |document|
|
35
|
+
create_document(document: document)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_document(document_id:)
|
40
|
+
url = V3_BASE_URL + "list?id=#{document_id}"
|
41
|
+
|
42
|
+
res = get_readwise_request(url)
|
43
|
+
|
44
|
+
res['results'].map { |item| transform_document(item) }.first
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_documents(updated_after: nil, location: nil, category: nil)
|
48
|
+
resp = documents_page(updated_after: updated_after, location: location, category: category)
|
49
|
+
next_page_cursor = resp[:next_page_cursor]
|
50
|
+
results = resp[:results]
|
51
|
+
while next_page_cursor
|
52
|
+
resp = documents_page(updated_after: updated_after, location: location, category: category, page_cursor: next_page_cursor)
|
53
|
+
results.concat(resp[:results])
|
54
|
+
next_page_cursor = resp[:next_page_cursor]
|
55
|
+
end
|
56
|
+
results.sort_by(&:created_at)
|
57
|
+
end
|
58
|
+
|
18
59
|
def create_highlight(highlight:)
|
19
|
-
create_highlights([highlight])
|
60
|
+
create_highlights(highlights: [highlight]).first
|
20
61
|
end
|
21
62
|
|
22
63
|
def create_highlights(highlights: [])
|
23
|
-
raise
|
64
|
+
raise ArgumentError unless highlights.all? { |item| item.is_a?(Readwise::HighlightCreate) }
|
65
|
+
return [] unless highlights.any?
|
66
|
+
|
67
|
+
url = BASE_URL + 'highlights/'
|
68
|
+
|
69
|
+
payload = { highlights: highlights.map(&:serialize) }
|
70
|
+
res = post_readwise_request(url, payload: payload)
|
71
|
+
|
72
|
+
modified_ids = res.map { |book| book['modified_highlights'] }.flatten
|
73
|
+
modified_ids.map { |id| get_highlight(highlight_id: id) }
|
24
74
|
end
|
25
75
|
|
26
76
|
def get_highlight(highlight_id:)
|
27
|
-
url = BASE_URL +
|
77
|
+
url = BASE_URL + "highlights/#{highlight_id}"
|
28
78
|
|
29
79
|
res = get_readwise_request(url)
|
30
80
|
|
31
81
|
transform_highlight(res)
|
32
82
|
end
|
33
83
|
|
84
|
+
def update_highlight(highlight:, update:)
|
85
|
+
raise ArgumentError unless update.is_a?(Readwise::HighlightUpdate)
|
86
|
+
|
87
|
+
url = BASE_URL + "highlights/#{highlight.highlight_id}"
|
88
|
+
|
89
|
+
res = patch_readwise_request(url, payload: update.serialize)
|
90
|
+
|
91
|
+
transform_highlight(res)
|
92
|
+
end
|
93
|
+
|
94
|
+
def remove_highlight_tag(highlight:, tag:)
|
95
|
+
url = BASE_URL + "highlights/#{highlight.highlight_id}/tags/#{tag.tag_id}"
|
96
|
+
|
97
|
+
delete_readwise_request(url)
|
98
|
+
end
|
99
|
+
|
100
|
+
def add_highlight_tag(highlight:, tag:)
|
101
|
+
raise ArgumentError unless tag.is_a?(Readwise::Tag)
|
102
|
+
|
103
|
+
url = BASE_URL + "highlights/#{highlight.highlight_id}/tags"
|
104
|
+
|
105
|
+
payload = tag.serialize.select { |k, v| k == :name }
|
106
|
+
res = post_readwise_request(url, payload: payload)
|
107
|
+
|
108
|
+
transform_tag(res)
|
109
|
+
end
|
110
|
+
|
111
|
+
def update_highlight_tag(highlight:, tag:)
|
112
|
+
raise ArgumentError unless tag.is_a?(Readwise::Tag)
|
113
|
+
|
114
|
+
url = BASE_URL + "highlights/#{highlight.highlight_id}/tags/#{tag.tag_id}"
|
115
|
+
|
116
|
+
payload = tag.serialize.select { |k, v| k == :name }
|
117
|
+
res = patch_readwise_request(url, payload: payload)
|
118
|
+
|
119
|
+
transform_tag(res)
|
120
|
+
end
|
121
|
+
|
122
|
+
def get_book(book_id:)
|
123
|
+
url = BASE_URL + "books/#{book_id}"
|
124
|
+
|
125
|
+
res = get_readwise_request(url)
|
126
|
+
|
127
|
+
transform_book(res)
|
128
|
+
end
|
129
|
+
|
130
|
+
def daily_review
|
131
|
+
url = BASE_URL + 'review/'
|
132
|
+
|
133
|
+
res = get_readwise_request(url)
|
134
|
+
transform_review(res)
|
135
|
+
end
|
136
|
+
|
34
137
|
def export(updated_after: nil, book_ids: [])
|
35
138
|
resp = export_page(updated_after: updated_after, book_ids: book_ids)
|
36
139
|
next_page_cursor = resp[:next_page_cursor]
|
@@ -45,25 +148,32 @@ module Readwise
|
|
45
148
|
|
46
149
|
private
|
47
150
|
|
151
|
+
def documents_page(page_cursor: nil, updated_after:, location:, category:)
|
152
|
+
parsed_body = get_documents_page(page_cursor: page_cursor, updated_after: updated_after, location: location, category: category)
|
153
|
+
results = parsed_body.dig('results').map do |item|
|
154
|
+
transform_document(item)
|
155
|
+
end
|
156
|
+
{
|
157
|
+
results: results,
|
158
|
+
next_page_cursor: parsed_body.dig('nextPageCursor')
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
162
|
+
def get_documents_page(page_cursor: nil, updated_after:, location:, category:)
|
163
|
+
params = {}
|
164
|
+
params['updatedAfter'] = updated_after if updated_after
|
165
|
+
params['location'] = location if location
|
166
|
+
params['category'] = category if category
|
167
|
+
params['pageCursor'] = page_cursor if page_cursor
|
168
|
+
url = V3_BASE_URL + 'list/?' + URI.encode_www_form(params)
|
169
|
+
|
170
|
+
get_readwise_request(url)
|
171
|
+
end
|
172
|
+
|
48
173
|
def export_page(page_cursor: nil, updated_after: nil, book_ids: [])
|
49
174
|
parsed_body = get_export_page(page_cursor: page_cursor, updated_after: updated_after, book_ids: book_ids)
|
50
175
|
results = parsed_body.dig('results').map do |item|
|
51
|
-
|
52
|
-
asin: item['asin'],
|
53
|
-
author: item['author'],
|
54
|
-
book_id: item['user_book_id'].to_s,
|
55
|
-
category: item['category'],
|
56
|
-
cover_image_url: item['cover_image_url'],
|
57
|
-
note: item['document_note'],
|
58
|
-
readable_title: item['readable_title'],
|
59
|
-
readwise_url: item['readwise_url'],
|
60
|
-
source: item['source'],
|
61
|
-
source_url: item['source_url'],
|
62
|
-
tags: item['book_tags'].map { |tag| transform_tag(tag) },
|
63
|
-
title: item['title'],
|
64
|
-
unique_url: item['unique_url'],
|
65
|
-
highlights: item['highlights'].map { |highlight| transform_highlight(highlight) },
|
66
|
-
)
|
176
|
+
transform_book(item)
|
67
177
|
end
|
68
178
|
{
|
69
179
|
results: results,
|
@@ -79,7 +189,27 @@ module Readwise
|
|
79
189
|
url = BASE_URL + 'export/?' + URI.encode_www_form(params)
|
80
190
|
|
81
191
|
get_readwise_request(url)
|
192
|
+
end
|
82
193
|
|
194
|
+
def transform_book(res)
|
195
|
+
highlights = (res['highlights'] || []).map { |highlight| transform_highlight(highlight) }
|
196
|
+
Book.new(
|
197
|
+
asin: res['asin'],
|
198
|
+
author: res['author'],
|
199
|
+
book_id: res['user_book_id'].to_s,
|
200
|
+
category: res['category'],
|
201
|
+
cover_image_url: res['cover_image_url'],
|
202
|
+
note: res['document_note'],
|
203
|
+
readable_title: res['readable_title'],
|
204
|
+
readwise_url: res['readwise_url'] || res['highlights_url'],
|
205
|
+
source: res['source'],
|
206
|
+
source_url: res['source_url'],
|
207
|
+
tags: (res['book_tags'] || res['tags'] || []).map { |tag| transform_tag(tag) },
|
208
|
+
title: res['title'],
|
209
|
+
unique_url: res['unique_url'],
|
210
|
+
highlights: highlights,
|
211
|
+
num_highlights: res['num_highlights'] || highlights.size,
|
212
|
+
)
|
83
213
|
end
|
84
214
|
|
85
215
|
def transform_highlight(res)
|
@@ -104,13 +234,66 @@ module Readwise
|
|
104
234
|
)
|
105
235
|
end
|
106
236
|
|
237
|
+
def transform_document(res)
|
238
|
+
Document.new(
|
239
|
+
author: res['author'],
|
240
|
+
category: res['category'],
|
241
|
+
created_at: res['created_at'],
|
242
|
+
html: res['html'],
|
243
|
+
id: res['id'].to_s,
|
244
|
+
image_url: res['image_url'],
|
245
|
+
location: res['location'],
|
246
|
+
notes: res['notes'],
|
247
|
+
parent_id: res['parent_id'],
|
248
|
+
published_date: res['published_date'],
|
249
|
+
reading_progress: res['reading_progress'],
|
250
|
+
site_name: res['site_name'],
|
251
|
+
source: res['source'],
|
252
|
+
source_url: res['source_url'],
|
253
|
+
summary: res['summary'],
|
254
|
+
tags: transform_tags(res['tags']),
|
255
|
+
title: res['title'],
|
256
|
+
updated_at: res['updated_at'],
|
257
|
+
url: res['url'],
|
258
|
+
word_count: res['word_count'],
|
259
|
+
)
|
260
|
+
end
|
261
|
+
|
262
|
+
def transform_tags(res)
|
263
|
+
if res.is_a?(Array)
|
264
|
+
res.map { |tag| transform_tag(tag) }
|
265
|
+
elsif res.is_a?(Hash)
|
266
|
+
res.map do |tag_id, tag|
|
267
|
+
tag['id'] = tag_id
|
268
|
+
transform_tag(tag)
|
269
|
+
end
|
270
|
+
else
|
271
|
+
[]
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
107
275
|
def transform_tag(res)
|
276
|
+
if res.is_a?(String)
|
277
|
+
return Tag.new(name: res)
|
278
|
+
end
|
279
|
+
|
108
280
|
Tag.new(
|
109
|
-
tag_id: res['id']
|
281
|
+
tag_id: res['id']&.to_s,
|
110
282
|
name: res['name'],
|
111
283
|
)
|
112
284
|
end
|
113
285
|
|
286
|
+
def transform_review(res)
|
287
|
+
highlights = (res['highlights'] || []).map { |highlight| transform_highlight(highlight) }
|
288
|
+
|
289
|
+
Review.new(
|
290
|
+
id: res['review_id'],
|
291
|
+
url: res['review_url'],
|
292
|
+
completed: res['review_completed'],
|
293
|
+
highlights: highlights
|
294
|
+
)
|
295
|
+
end
|
296
|
+
|
114
297
|
def get_readwise_request(url)
|
115
298
|
uri = URI.parse(url)
|
116
299
|
req = Net::HTTP::Get.new(uri)
|
@@ -118,10 +301,51 @@ module Readwise
|
|
118
301
|
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
119
302
|
http.request(req)
|
120
303
|
end
|
121
|
-
|
122
|
-
raise Error,
|
123
|
-
|
304
|
+
|
305
|
+
raise Error, "Get request failed with status code: #{res.code}" unless res.is_a?(Net::HTTPSuccess)
|
306
|
+
|
307
|
+
JSON.parse(res.body)
|
308
|
+
end
|
309
|
+
|
310
|
+
def patch_readwise_request(url, payload:)
|
311
|
+
uri = URI.parse(url)
|
312
|
+
req = Net::HTTP::Patch.new(uri)
|
313
|
+
req['Authorization'] = "Token #{@token}"
|
314
|
+
req['Content-Type'] = 'application/json'
|
315
|
+
req.body = payload.to_json
|
316
|
+
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
317
|
+
http.request(req)
|
318
|
+
end
|
319
|
+
|
320
|
+
raise Error, 'Patch request failed' unless res.is_a?(Net::HTTPSuccess)
|
321
|
+
|
124
322
|
JSON.parse(res.body)
|
125
323
|
end
|
324
|
+
|
325
|
+
def post_readwise_request(url, payload:)
|
326
|
+
uri = URI.parse(url)
|
327
|
+
req = Net::HTTP::Post.new(uri)
|
328
|
+
req['Authorization'] = "Token #{@token}"
|
329
|
+
req['Content-Type'] = 'application/json'
|
330
|
+
req.body = payload.to_json
|
331
|
+
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
332
|
+
http.request(req)
|
333
|
+
end
|
334
|
+
|
335
|
+
raise Error, 'Post request failed' unless res.is_a?(Net::HTTPSuccess)
|
336
|
+
|
337
|
+
JSON.parse(res.body)
|
338
|
+
end
|
339
|
+
|
340
|
+
def delete_readwise_request(url)
|
341
|
+
uri = URI.parse(url)
|
342
|
+
req = Net::HTTP::Delete.new(uri)
|
343
|
+
req['Authorization'] = "Token #{@token}"
|
344
|
+
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
345
|
+
http.request(req)
|
346
|
+
end
|
347
|
+
|
348
|
+
raise Error, 'Delete request failed' unless res.is_a?(Net::HTTPSuccess)
|
349
|
+
end
|
126
350
|
end
|
127
351
|
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Readwise
|
4
|
+
Document = Struct.new(
|
5
|
+
'ReadwiseDocument',
|
6
|
+
:author,
|
7
|
+
:category, # One of: article, email, rss, highlight, note, pdf, epub, tweet or video.
|
8
|
+
# Default is guessed based on the URL, usually article.
|
9
|
+
:created_at,
|
10
|
+
:html,
|
11
|
+
:id,
|
12
|
+
:image_url,
|
13
|
+
:location, # One of: new, later, archive or feed. Default is new.
|
14
|
+
:notes,
|
15
|
+
:published_date,
|
16
|
+
:reading_progress,
|
17
|
+
:site_name,
|
18
|
+
:source,
|
19
|
+
:source_url,
|
20
|
+
:summary,
|
21
|
+
:tags,
|
22
|
+
:title,
|
23
|
+
:updated_at,
|
24
|
+
:url,
|
25
|
+
:word_count,
|
26
|
+
:parent_id, # both highlights and notes made in Reader are also considered Documents.
|
27
|
+
# Highlights and notes will have `parent_id` set, which is the Document id
|
28
|
+
# of the article/book/etc and highlight that they belong to, respectively.
|
29
|
+
keyword_init: true
|
30
|
+
) do
|
31
|
+
def created_at_time
|
32
|
+
return unless created_at
|
33
|
+
|
34
|
+
Time.parse(created_at)
|
35
|
+
end
|
36
|
+
|
37
|
+
def updated_at_time
|
38
|
+
return unless updated_at
|
39
|
+
|
40
|
+
Time.parse(updated_at)
|
41
|
+
end
|
42
|
+
|
43
|
+
def published_date_time
|
44
|
+
return unless published_date
|
45
|
+
|
46
|
+
Time.at(published_date/1000)
|
47
|
+
end
|
48
|
+
|
49
|
+
def read?(threshold: 0.85)
|
50
|
+
reading_progress >= threshold
|
51
|
+
end
|
52
|
+
|
53
|
+
def parent?
|
54
|
+
parent_id.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
def child?
|
58
|
+
!parent?
|
59
|
+
end
|
60
|
+
|
61
|
+
def in_new?
|
62
|
+
location == 'new'
|
63
|
+
end
|
64
|
+
|
65
|
+
def in_later?
|
66
|
+
location == 'later'
|
67
|
+
end
|
68
|
+
|
69
|
+
def in_archive?
|
70
|
+
location == 'archive'
|
71
|
+
end
|
72
|
+
|
73
|
+
def pdf?
|
74
|
+
category == 'pdf'
|
75
|
+
end
|
76
|
+
|
77
|
+
def epub?
|
78
|
+
category == 'epub'
|
79
|
+
end
|
80
|
+
|
81
|
+
def tweet?
|
82
|
+
category == 'tweet'
|
83
|
+
end
|
84
|
+
|
85
|
+
def video?
|
86
|
+
category == 'video'
|
87
|
+
end
|
88
|
+
|
89
|
+
def article?
|
90
|
+
category == 'article'
|
91
|
+
end
|
92
|
+
|
93
|
+
def book?
|
94
|
+
category == 'book'
|
95
|
+
end
|
96
|
+
|
97
|
+
def email?
|
98
|
+
category == 'email'
|
99
|
+
end
|
100
|
+
|
101
|
+
def rss?
|
102
|
+
category == 'rss'
|
103
|
+
end
|
104
|
+
|
105
|
+
def highlight?
|
106
|
+
category == 'highlight'
|
107
|
+
end
|
108
|
+
|
109
|
+
def note?
|
110
|
+
category == 'note'
|
111
|
+
end
|
112
|
+
|
113
|
+
def serialize
|
114
|
+
to_h
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
DocumentCreate = Struct.new(
|
119
|
+
'ReadwiseDocumentCreate',
|
120
|
+
:author,
|
121
|
+
:category, # One of: article, email, rss, highlight, note, pdf, epub, tweet or video.
|
122
|
+
# Default is guessed based on the URL, usually article.
|
123
|
+
:html,
|
124
|
+
:image_url,
|
125
|
+
:location, # One of: new, later, archive or feed. Default is new.
|
126
|
+
:notes,
|
127
|
+
:published_date,
|
128
|
+
:saved_using,
|
129
|
+
:should_clean_html,
|
130
|
+
:summary,
|
131
|
+
:tags,
|
132
|
+
:title,
|
133
|
+
:url,
|
134
|
+
keyword_init: true
|
135
|
+
) do
|
136
|
+
def serialize
|
137
|
+
to_h.compact
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/lib/readwise/highlight.rb
CHANGED
@@ -39,5 +39,45 @@ module Readwise
|
|
39
39
|
|
40
40
|
Time.parse(highlighted_at)
|
41
41
|
end
|
42
|
+
|
43
|
+
def serialize
|
44
|
+
to_h
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
HighlightCreate = Struct.new(
|
49
|
+
'ReadwiseHighlightCreate',
|
50
|
+
:author,
|
51
|
+
:category, # One of: books, articles, tweets or podcasts.
|
52
|
+
# (default: articles when source_url is provided, otherwise: books)
|
53
|
+
:highlight_url,
|
54
|
+
:highlighted_at,
|
55
|
+
:image_url,
|
56
|
+
:location,
|
57
|
+
:location_type, # One of: page, order or time_offset (default: order)
|
58
|
+
:note,
|
59
|
+
:source_type,
|
60
|
+
:source_url,
|
61
|
+
:text,
|
62
|
+
:title,
|
63
|
+
keyword_init: true
|
64
|
+
) do
|
65
|
+
def serialize
|
66
|
+
to_h.compact
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
HighlightUpdate = Struct.new(
|
71
|
+
'ReadwiseHighlightUpdate',
|
72
|
+
:color,
|
73
|
+
:location,
|
74
|
+
:note,
|
75
|
+
:text,
|
76
|
+
:url,
|
77
|
+
keyword_init: true
|
78
|
+
) do
|
79
|
+
def serialize
|
80
|
+
to_h.compact
|
81
|
+
end
|
42
82
|
end
|
43
83
|
end
|
data/lib/readwise/tag.rb
CHANGED
data/lib/readwise/version.rb
CHANGED
data/lib/readwise.rb
CHANGED
data/readwise.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
|
12
12
|
spec.summary = "Readwise API client"
|
13
13
|
spec.description = "Minimal Readwise API client and highlight parsing library"
|
14
|
-
spec.homepage = "https://github.com/
|
14
|
+
spec.homepage = "https://github.com/joshbeckman/readwise-ruby"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
17
17
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.metadata["homepage_uri"] = spec.homepage
|
23
23
|
spec.metadata["source_code_uri"] = spec.homepage
|
24
24
|
spec.metadata["changelog_uri"] = spec.homepage + "/blob/main/CHANGELOG.md"
|
25
|
+
spec.metadata["funding_uri"] = "https://github.com/sponsors/joshbeckman"
|
25
26
|
else
|
26
27
|
raise "RubyGems 2.0 or newer is required to protect against " \
|
27
28
|
"public gem pushes."
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: readwise
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josh Beckman
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: bundler
|
@@ -87,19 +86,21 @@ files:
|
|
87
86
|
- lib/readwise.rb
|
88
87
|
- lib/readwise/book.rb
|
89
88
|
- lib/readwise/client.rb
|
89
|
+
- lib/readwise/document.rb
|
90
90
|
- lib/readwise/highlight.rb
|
91
|
+
- lib/readwise/review.rb
|
91
92
|
- lib/readwise/tag.rb
|
92
93
|
- lib/readwise/version.rb
|
93
94
|
- readwise.gemspec
|
94
|
-
homepage: https://github.com/
|
95
|
+
homepage: https://github.com/joshbeckman/readwise-ruby
|
95
96
|
licenses:
|
96
97
|
- MIT
|
97
98
|
metadata:
|
98
99
|
allowed_push_host: https://rubygems.org
|
99
|
-
homepage_uri: https://github.com/
|
100
|
-
source_code_uri: https://github.com/
|
101
|
-
changelog_uri: https://github.com/
|
102
|
-
|
100
|
+
homepage_uri: https://github.com/joshbeckman/readwise-ruby
|
101
|
+
source_code_uri: https://github.com/joshbeckman/readwise-ruby
|
102
|
+
changelog_uri: https://github.com/joshbeckman/readwise-ruby/blob/main/CHANGELOG.md
|
103
|
+
funding_uri: https://github.com/sponsors/joshbeckman
|
103
104
|
rdoc_options: []
|
104
105
|
require_paths:
|
105
106
|
- lib
|
@@ -114,8 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
115
|
- !ruby/object:Gem::Version
|
115
116
|
version: '0'
|
116
117
|
requirements: []
|
117
|
-
rubygems_version: 3.
|
118
|
-
signing_key:
|
118
|
+
rubygems_version: 3.6.9
|
119
119
|
specification_version: 4
|
120
120
|
summary: Readwise API client
|
121
121
|
test_files: []
|