hatenablog 0.5.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/build.yml +49 -0
- data/.gitignore +4 -3
- data/CHANGELOG.md +21 -0
- data/Gemfile +4 -1
- data/README.md +60 -44
- data/Rakefile +1 -0
- data/Steepfile +11 -0
- data/exe/{get_access_token → get_hatena_oauth_access_token} +3 -1
- data/hatenablog.gemspec +1 -1
- data/lib/hatenablog/category.rb +6 -3
- data/lib/hatenablog/client.rb +37 -8
- data/lib/hatenablog/configuration.rb +2 -0
- data/lib/hatenablog/entry.rb +25 -5
- data/lib/hatenablog/feed.rb +2 -1
- data/lib/hatenablog/requester.rb +12 -7
- data/lib/hatenablog/version.rb +1 -1
- data/rbs_collection.lock.yaml +36 -0
- data/rbs_collection.yaml +36 -0
- data/sig/hatenablog.rbs +249 -0
- metadata +15 -11
- data/.travis.yml +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5c6ebd627cfd566a53f9423b632b3ef6f42d285e2ffa84b176af438022a78bbe
|
4
|
+
data.tar.gz: c7dbd064aee0d3557c17464be93b351cc741b973c16ede5dd6d8cf6afde30bfc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe094c01e6c51c1b41bd674bc6aa891767532c3472135312709d090bd1301dfcc3041fb95e90a7a641c20ea07f5a32403d6d006a13a8a6c16aa319c74ad1c454
|
7
|
+
data.tar.gz: 8c86b99f15106170419012c8e8d15c90b1a2644488a62b16cfb76defad288ca81cb04a920f772d8fedf314f88ff8c4351889439a766e59305caad5134d0d15cf
|
@@ -0,0 +1,49 @@
|
|
1
|
+
name: build
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request:
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
build:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
strategy:
|
13
|
+
matrix:
|
14
|
+
ruby: ['2.6', '2.7', '3.0', '3.1.0-preview1']
|
15
|
+
include:
|
16
|
+
- ruby: '3.0'
|
17
|
+
report-coverage: true
|
18
|
+
steps:
|
19
|
+
- uses: actions/checkout@v2
|
20
|
+
- uses: ruby/setup-ruby@v1
|
21
|
+
with:
|
22
|
+
ruby-version: ${{ matrix.ruby }}
|
23
|
+
- run: gem install bundler
|
24
|
+
- run: bundle install -j4
|
25
|
+
- name: Run tests and report test coverage
|
26
|
+
if: matrix.report-coverage
|
27
|
+
uses: paambaati/codeclimate-action@v2.7.5
|
28
|
+
env:
|
29
|
+
CC_TEST_REPORTER_ID: 309cf0784d00d2a6009566d28be111a8a0280cdeb2da280225eedf577b16beb5
|
30
|
+
with:
|
31
|
+
coverageCommand: bundle exec rake
|
32
|
+
coverageLocations: ${{github.workspace}}/coverage/coverage.json:simplecov
|
33
|
+
- name: Run tests
|
34
|
+
if: "!matrix.report-coverage"
|
35
|
+
env:
|
36
|
+
TZ: Asia/Tokyo
|
37
|
+
run: bundle exec rake
|
38
|
+
steep:
|
39
|
+
runs-on: ubuntu-latest
|
40
|
+
steps:
|
41
|
+
- uses: actions/checkout@v2
|
42
|
+
with:
|
43
|
+
submodules: 'true'
|
44
|
+
- uses: ruby/setup-ruby@v1
|
45
|
+
with:
|
46
|
+
ruby-version: '3.0'
|
47
|
+
- run: bundle install -j4
|
48
|
+
- run: rbs collection install
|
49
|
+
- run: bundle exec steep check
|
data/.gitignore
CHANGED
@@ -28,9 +28,10 @@ build/
|
|
28
28
|
|
29
29
|
# for a library or gem, you might want to ignore these files since the code is
|
30
30
|
# intended to run in multiple environments; otherwise, check them in:
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
Gemfile.lock
|
32
|
+
.ruby-version
|
33
|
+
.ruby-gemset
|
34
34
|
|
35
35
|
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
36
36
|
.rvmrc
|
37
|
+
/.gem_rbs_collection/
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## Unreleased
|
8
|
+
|
9
|
+
## 0.8.0 - 2021-11-20
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- [Create the signature with RBS](https://github.com/kymmt90/hatenablog/pull/24)
|
14
|
+
- This is experimental
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
|
18
|
+
- [Change the executable name to more specific one](https://github.com/kymmt90/hatenablog/pull/26)
|
19
|
+
- From `get_access_token` to `get_hatena_oauth_access_token`
|
20
|
+
- [Use only supported Rubies in 2021-11](https://github.com/kymmt90/hatenablog/pull/27)
|
21
|
+
- Drop Ruby 2.4 and 2.5 and add Ruby 3.1.0-preview1
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Hatenablog
|
2
2
|
|
3
|
-
[![Gem Version](https://badge.fury.io/rb/hatenablog.svg)](https://badge.fury.io/rb/hatenablog) [![Build Status](https://
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/hatenablog.svg)](https://badge.fury.io/rb/hatenablog) [![Build Status](https://github.com/kymmt90/hatenablog/workflows/build/badge.svg)](https://github.com/kymmt90/hatenablog/actions?workflow=build)
|
4
4
|
[![Code Climate](https://codeclimate.com/github/kymmt90/hatenablog/badges/gpa.svg)](https://codeclimate.com/github/kymmt90/hatenablog)
|
5
5
|
[![Test Coverage](https://codeclimate.com/github/kymmt90/hatenablog/badges/coverage.svg)](https://codeclimate.com/github/kymmt90/hatenablog/coverage)
|
6
6
|
|
@@ -19,15 +19,21 @@ This gem supports following operations through OAuth 1.0a or Basic authenticatio
|
|
19
19
|
|
20
20
|
Add this line to your application's Gemfile:
|
21
21
|
|
22
|
-
|
22
|
+
```
|
23
|
+
gem 'hatenablog'
|
24
|
+
```
|
23
25
|
|
24
26
|
And then execute:
|
25
27
|
|
26
|
-
|
28
|
+
```
|
29
|
+
$ bundle
|
30
|
+
```
|
27
31
|
|
28
32
|
Or install it yourself as:
|
29
33
|
|
30
|
-
|
34
|
+
```
|
35
|
+
$ gem install hatenablog
|
36
|
+
```
|
31
37
|
|
32
38
|
### Get OAuth credentials
|
33
39
|
|
@@ -41,11 +47,13 @@ Access [Hatena application registoration page](http://developer.hatena.ne.jp/) a
|
|
41
47
|
|
42
48
|
Execute this command:
|
43
49
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
50
|
+
```
|
51
|
+
$ get_hatena_oauth_access_token <your consumer key> <your consumer secret>
|
52
|
+
Visit this website and get the PIN: https://www.hatena.com/oauth/authorize?oauth_token=XXXXXXXXXXXXXXXXXXXX
|
53
|
+
Enter the PIN: <your PIN> [Enter]
|
54
|
+
Access token: <your access token>
|
55
|
+
Access token secret: <your access token secret>
|
56
|
+
```
|
49
57
|
|
50
58
|
#### 3. [Optional] Set up the YAML configuration file
|
51
59
|
|
@@ -99,15 +107,19 @@ Hatenablog::Client.create do |blog|
|
|
99
107
|
end
|
100
108
|
|
101
109
|
# Post new entry
|
102
|
-
posted_entry = blog.post_entry(
|
103
|
-
|
104
|
-
|
110
|
+
posted_entry = blog.post_entry(
|
111
|
+
'Entry Title',
|
112
|
+
'This is entry contents', # markdown form
|
113
|
+
['Test', 'Programming'] # categories
|
114
|
+
)
|
105
115
|
|
106
116
|
# Update entry
|
107
|
-
updated_entry = blog.update_entry(
|
108
|
-
|
109
|
-
|
110
|
-
|
117
|
+
updated_entry = blog.update_entry(
|
118
|
+
posted_entry.id,
|
119
|
+
'Revised Entry Title',
|
120
|
+
posted_entry.content,
|
121
|
+
posted_entry.categories
|
122
|
+
)
|
111
123
|
|
112
124
|
# Delete entry
|
113
125
|
blog.delete_entry(updated_entry.id)
|
@@ -148,8 +160,8 @@ end
|
|
148
160
|
### Blog
|
149
161
|
|
150
162
|
```ruby
|
151
|
-
client.title
|
152
|
-
client.author_name
|
163
|
+
client.title # Get the blog title
|
164
|
+
client.author_name # Get the blog author name
|
153
165
|
```
|
154
166
|
|
155
167
|
### Feeds
|
@@ -157,16 +169,16 @@ client.author_name # Get the blog author name
|
|
157
169
|
```ruby
|
158
170
|
feed = client.next_feed # Get the first feed when no argument is passed
|
159
171
|
feed.uri
|
160
|
-
feed.next_uri
|
172
|
+
feed.next_uri # The next feed URI
|
161
173
|
feed.title
|
162
174
|
feed.author_name
|
163
|
-
feed.
|
175
|
+
feed.updated # Updated datetime
|
164
176
|
|
165
|
-
feed.entries
|
177
|
+
feed.entries # entries in the feed
|
166
178
|
feed.each_entry do |entry|
|
167
179
|
# ...
|
168
180
|
end
|
169
|
-
feed.has_next?
|
181
|
+
feed.has_next? # true if the next page exists
|
170
182
|
next_feed = client.next_feed(feed)
|
171
183
|
```
|
172
184
|
|
@@ -174,38 +186,42 @@ next_feed = client.next_feed(feed)
|
|
174
186
|
|
175
187
|
```ruby
|
176
188
|
client.get_entry('0000000000000000000') # Get the entry specifed by its ID
|
177
|
-
client.entries
|
178
|
-
client.entries(1)
|
179
|
-
client.all_entries
|
180
|
-
|
181
|
-
entry = client.post_entry(
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
189
|
+
client.entries # Get blog entries in the first page
|
190
|
+
client.entries(1) # Get blog entries in the first and the second page
|
191
|
+
client.all_entries # Get all entries in the blog
|
192
|
+
|
193
|
+
entry = client.post_entry(
|
194
|
+
'Example Title', # title
|
195
|
+
'This is the **example** entry.', # content
|
196
|
+
['Ruby', 'Rails'], # categories
|
197
|
+
'yes' # draft
|
198
|
+
)
|
186
199
|
entry.id
|
187
200
|
entry.uri
|
188
201
|
entry.edit_uri
|
189
202
|
entry.author_name
|
190
|
-
entry.title
|
191
|
-
entry.content
|
192
|
-
entry.
|
193
|
-
entry.draft
|
194
|
-
entry.
|
195
|
-
entry.
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
203
|
+
entry.title #=> 'Example Title'
|
204
|
+
entry.content #=> 'This is the **example** entry.'
|
205
|
+
entry.formatted_content #=> '<p>This is the <strong>example</strong> entry.</p>'
|
206
|
+
entry.draft #=> 'yes'
|
207
|
+
entry.draft? #=> true
|
208
|
+
entry.categories #=> ['Ruby', 'Rails']
|
209
|
+
entry.updated # Updated datetime
|
210
|
+
|
211
|
+
client.update_entry(
|
212
|
+
entry.id,
|
213
|
+
entry.title,
|
214
|
+
'This is the **modified** example entry.',
|
215
|
+
entry.categories,
|
216
|
+
'no'
|
217
|
+
)
|
202
218
|
client.delete_entry(entry.id)
|
203
219
|
```
|
204
220
|
|
205
221
|
### Categories
|
206
222
|
|
207
223
|
```ruby
|
208
|
-
categories = client.categories
|
224
|
+
categories = client.categories # Get categories registered in the blog
|
209
225
|
categories.each do |cat|
|
210
226
|
puts cat
|
211
227
|
end
|
data/Rakefile
CHANGED
data/Steepfile
ADDED
@@ -10,6 +10,7 @@ class AccessTokenGetter
|
|
10
10
|
SITE_URI = 'https://www.hatena.com'
|
11
11
|
REQUEST_TOKEN_URI = '/oauth/initiate?scope=read_public%2Cread_private%2Cwrite_public%2Cwrite_private'
|
12
12
|
ACCESS_TOKEN_URI = '/oauth/token'
|
13
|
+
AUTHORIZE_URI = 'https://www.hatena.ne.jp/oauth/authorize'
|
13
14
|
|
14
15
|
def initialize(consumer_key, consumer_secret)
|
15
16
|
@consumer_key = consumer_key
|
@@ -19,7 +20,8 @@ class AccessTokenGetter
|
|
19
20
|
oauth_callback: 'oob',
|
20
21
|
site: SITE_URI,
|
21
22
|
request_token_url: REQUEST_TOKEN_URI,
|
22
|
-
access_token_url: ACCESS_TOKEN_URI
|
23
|
+
access_token_url: ACCESS_TOKEN_URI,
|
24
|
+
authorize_url: AUTHORIZE_URI)
|
23
25
|
end
|
24
26
|
|
25
27
|
def get_request_token
|
data/hatenablog.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
-
spec.required_ruby_version = '>= 2.
|
22
|
+
spec.required_ruby_version = '>= 2.3'
|
23
23
|
|
24
24
|
spec.add_development_dependency "bundler"
|
25
25
|
spec.add_development_dependency "rake"
|
data/lib/hatenablog/category.rb
CHANGED
@@ -15,9 +15,12 @@ module Hatenablog
|
|
15
15
|
@categories.dup
|
16
16
|
end
|
17
17
|
|
18
|
-
def each
|
18
|
+
def each(&block)
|
19
|
+
return enum_for unless block_given?
|
20
|
+
|
19
21
|
@categories.each do |category|
|
20
|
-
|
22
|
+
# @type var block: ^(String) -> void
|
23
|
+
block.call(category)
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
@@ -36,7 +39,7 @@ module Hatenablog
|
|
36
39
|
end
|
37
40
|
|
38
41
|
def parse_document
|
39
|
-
@categories = @document.css('atom|category').inject(
|
42
|
+
@categories = @document.css('atom|category').inject(Array.new) do |categories, category|
|
40
43
|
categories << category['term'].to_s
|
41
44
|
end
|
42
45
|
|
data/lib/hatenablog/client.rb
CHANGED
@@ -12,6 +12,7 @@ module Hatenablog
|
|
12
12
|
MEMBER_URI = "https://blog.hatena.ne.jp/%s/%s/atom/entry/%s".freeze
|
13
13
|
CATEGORY_URI = "https://blog.hatena.ne.jp/%s/%s/atom/category".freeze
|
14
14
|
|
15
|
+
# @dynamic requester=
|
15
16
|
attr_writer :requester
|
16
17
|
|
17
18
|
# Create a new hatenablog AtomPub client from a configuration file.
|
@@ -24,9 +25,9 @@ module Hatenablog
|
|
24
25
|
yield blog
|
25
26
|
end
|
26
27
|
|
27
|
-
def initialize(config =
|
28
|
+
def initialize(config = Configuration.new)
|
28
29
|
if block_given?
|
29
|
-
yield config
|
30
|
+
yield config
|
30
31
|
config.check_valid_or_raise
|
31
32
|
end
|
32
33
|
@requester = Requester.create(config)
|
@@ -59,7 +60,7 @@ module Hatenablog
|
|
59
60
|
# Get all blog entries.
|
60
61
|
# @return [Hatenablog::Entries] enumerator of blog entries
|
61
62
|
def all_entries
|
62
|
-
Entries.new(self,
|
63
|
+
Entries.new(self, 0, :all)
|
63
64
|
end
|
64
65
|
|
65
66
|
# Get the next feed of the given feed.
|
@@ -161,7 +162,7 @@ module Hatenablog
|
|
161
162
|
xml.name author_name
|
162
163
|
end
|
163
164
|
xml.content(content, type: 'text/x-markdown')
|
164
|
-
xml.updated updated
|
165
|
+
xml.updated updated if updated && !updated.empty?
|
165
166
|
categories.each do |category|
|
166
167
|
xml.category(term: category)
|
167
168
|
end
|
@@ -207,17 +208,45 @@ module Hatenablog
|
|
207
208
|
class Entries
|
208
209
|
include Enumerable
|
209
210
|
|
210
|
-
def initialize(client, page = 0)
|
211
|
+
def initialize(client, page = 0, fetch = :partial)
|
211
212
|
@client = client
|
212
213
|
@page = page
|
214
|
+
@fetch = fetch
|
213
215
|
end
|
214
216
|
|
215
|
-
def each
|
217
|
+
def each(&block)
|
218
|
+
return enum_for unless block_given?
|
219
|
+
|
220
|
+
# @type var block: ^(Entry) -> void
|
221
|
+
if @fetch == :all
|
222
|
+
each_all(&block)
|
223
|
+
else
|
224
|
+
each_partial(&block)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
private
|
229
|
+
|
230
|
+
def each_all(&block)
|
231
|
+
feed = nil
|
232
|
+
|
233
|
+
while feed = @client.next_feed(feed)
|
234
|
+
feed.entries.each { |entry| block.call(entry) }
|
235
|
+
end
|
236
|
+
|
237
|
+
self
|
238
|
+
end
|
239
|
+
|
240
|
+
def each_partial(&block)
|
241
|
+
feed = nil
|
242
|
+
|
216
243
|
current_page = 0
|
217
|
-
|
218
|
-
feed.entries.each { |entry|
|
244
|
+
while current_page <= @page && feed = @client.next_feed(feed)
|
245
|
+
feed.entries.each { |entry| block.call(entry) }
|
219
246
|
current_page += 1
|
220
247
|
end
|
248
|
+
|
249
|
+
self
|
221
250
|
end
|
222
251
|
end
|
223
252
|
end
|
@@ -4,6 +4,8 @@ require 'ostruct'
|
|
4
4
|
|
5
5
|
module Hatenablog
|
6
6
|
class Configuration < OpenStruct
|
7
|
+
# @dynamic auth_type, consumer_key, consumer_secret, access_token, access_token_secret, api_key, user_id, blog_id
|
8
|
+
|
7
9
|
OAUTH_KEYS = %i(consumer_key consumer_secret access_token access_token_secret user_id blog_id)
|
8
10
|
BASIC_KEYS = %i(api_key user_id blog_id)
|
9
11
|
|
data/lib/hatenablog/entry.rb
CHANGED
@@ -3,6 +3,9 @@ require 'time'
|
|
3
3
|
|
4
4
|
module Hatenablog
|
5
5
|
module AfterHook
|
6
|
+
# @dynamic uri=, edit_uri=, author_name=, title=, content=, updated=, draft=, categories=
|
7
|
+
# @dynamic instance_methods, alias_method, define_method
|
8
|
+
|
6
9
|
# Register a hooking method for given methods.
|
7
10
|
# The hook method is executed after calling given methods.
|
8
11
|
# @param [Symbol] hooking method name
|
@@ -17,8 +20,10 @@ module Hatenablog
|
|
17
20
|
alias_method origin_method, method
|
18
21
|
|
19
22
|
define_method(method) do |*args, &block|
|
23
|
+
# @type var block: ^(*untyped) -> untyped
|
20
24
|
result = send(origin_method, *args, &block)
|
21
25
|
send(hook)
|
26
|
+
result
|
22
27
|
end
|
23
28
|
end
|
24
29
|
end
|
@@ -27,8 +32,14 @@ module Hatenablog
|
|
27
32
|
class Entry
|
28
33
|
extend AfterHook
|
29
34
|
|
30
|
-
|
31
|
-
|
35
|
+
# @dynamic uri, uri=, author_name, author_name=, title, title=, content, content=, draft, draft=
|
36
|
+
attr_accessor :uri, :author_name, :title, :content, :draft
|
37
|
+
|
38
|
+
# @dynamic edit_uri, id, updated
|
39
|
+
attr_reader :edit_uri, :id, :updated
|
40
|
+
|
41
|
+
# @dynamic categories=
|
42
|
+
attr_writer :categories
|
32
43
|
|
33
44
|
def updated=(date)
|
34
45
|
@updated = Time.parse(date)
|
@@ -87,6 +98,11 @@ module Hatenablog
|
|
87
98
|
@document.to_s.gsub(/\"/, "'")
|
88
99
|
end
|
89
100
|
|
101
|
+
# @return [String]
|
102
|
+
def formatted_content
|
103
|
+
@formatted_content
|
104
|
+
end
|
105
|
+
|
90
106
|
def self.build_xml(uri, edit_uri, author_name, title, content, draft, categories, updated)
|
91
107
|
builder = Nokogiri::XML::Builder.new(encoding: 'utf-8') do |xml|
|
92
108
|
xml.entry('xmlns' => 'http://www.w3.org/2005/Atom',
|
@@ -128,15 +144,19 @@ module Hatenablog
|
|
128
144
|
@author_name = @document.at_css('author name').content
|
129
145
|
@title = @document.at_css('title').content
|
130
146
|
@content = @document.at_css('content').content
|
147
|
+
@formatted_content = @document.xpath('//hatena:formatted-content', hatena: 'http://www.hatena.ne.jp/info/xmlns#')[0]
|
148
|
+
@formatted_content = @formatted_content.content if @formatted_content
|
131
149
|
@draft = @document.at_css('entry app|control app|draft').content
|
132
150
|
@categories = parse_categories
|
133
|
-
|
151
|
+
if @document.at_css('entry updated')
|
134
152
|
@updated = Time.parse(@document.at_css('entry updated').content)
|
153
|
+
else
|
154
|
+
@updated = nil
|
135
155
|
end
|
136
156
|
end
|
137
157
|
|
138
158
|
def parse_categories
|
139
|
-
categories = @document.css('category').inject(
|
159
|
+
categories = @document.css('category').inject(Array.new) do |categories, category|
|
140
160
|
categories << category['term'].to_s
|
141
161
|
end
|
142
162
|
categories
|
@@ -151,7 +171,7 @@ module Hatenablog
|
|
151
171
|
@document.at_css('entry app|control app|draft').content = @draft
|
152
172
|
|
153
173
|
unless @updated.nil? || @document.at_css('entry updated').nil?
|
154
|
-
@document.at_css('entry updated').content = @updated
|
174
|
+
@document.at_css('entry updated').content = @updated&.iso8601
|
155
175
|
end
|
156
176
|
|
157
177
|
unless @categories.nil?
|
data/lib/hatenablog/feed.rb
CHANGED
@@ -5,6 +5,7 @@ require 'hatenablog/entry'
|
|
5
5
|
|
6
6
|
module Hatenablog
|
7
7
|
class Feed
|
8
|
+
# @dynamic uri, next_uri, title, author_name, updated
|
8
9
|
attr_reader :uri, :next_uri, :title, :author_name, :updated
|
9
10
|
|
10
11
|
# Create a new blog feed from a XML string.
|
@@ -53,7 +54,7 @@ module Hatenablog
|
|
53
54
|
end
|
54
55
|
|
55
56
|
def parse_entry
|
56
|
-
@entries = @document.css('feed > entry').inject(
|
57
|
+
@entries = @document.css('feed > entry').inject(Array.new) do |entries, entry|
|
57
58
|
# add namespace 'app' to recognize XML correctly
|
58
59
|
entry['xmlns:app'] = 'http://www.w3.org/2007/app'
|
59
60
|
entries << Hatenablog::Entry.load_xml(entry.to_s)
|
data/lib/hatenablog/requester.rb
CHANGED
@@ -96,7 +96,7 @@ module Hatenablog
|
|
96
96
|
# @param [string] body HTTP request body
|
97
97
|
# @param [string] headers HTTP request headers
|
98
98
|
# @return [Net::HTTPResponse] HTTP response
|
99
|
-
def post(uri, body, headers =
|
99
|
+
def post(uri, body, headers = {})
|
100
100
|
request(uri, :post, body: body, headers: headers)
|
101
101
|
end
|
102
102
|
|
@@ -105,7 +105,7 @@ module Hatenablog
|
|
105
105
|
# @param [string] body HTTP request body
|
106
106
|
# @param [string] headers HTTP request headers
|
107
107
|
# @return [Net::HTTPResponse] HTTP response
|
108
|
-
def put(uri, body, headers =
|
108
|
+
def put(uri, body, headers = {})
|
109
109
|
request(uri, :put, body: body, headers: headers)
|
110
110
|
end
|
111
111
|
|
@@ -113,22 +113,27 @@ module Hatenablog
|
|
113
113
|
# @param [string] uri target URI
|
114
114
|
# @param [string] headers HTTP request headers
|
115
115
|
# @return [Net::HTTPResponse] HTTP response
|
116
|
-
def delete(uri, headers =
|
116
|
+
def delete(uri, headers = {})
|
117
117
|
request(uri, :delete, headers: headers)
|
118
118
|
end
|
119
119
|
|
120
120
|
private
|
121
|
-
|
121
|
+
|
122
|
+
def request(uri, method, body: nil, headers: {})
|
122
123
|
uri = URI(uri)
|
123
|
-
req = METHODS[method].new(uri, headers)
|
124
|
+
req = METHODS[method].new(uri.to_s, headers)
|
124
125
|
req.basic_auth @user_id, @api_key
|
125
126
|
if body
|
126
127
|
req.body = body
|
127
128
|
req.content_type = ATOM_CONTENT_TYPE
|
128
129
|
end
|
129
130
|
|
130
|
-
http = Net::HTTP.
|
131
|
-
http.
|
131
|
+
http = Net::HTTP.new(uri.hostname, uri.port)
|
132
|
+
http.use_ssl = uri.port == 443
|
133
|
+
http.start do |conn|
|
134
|
+
conn.request(req)
|
135
|
+
end
|
136
|
+
|
132
137
|
rescue => problem
|
133
138
|
raise RequestError, "Fail to #{method.upcase}: " + problem.to_s
|
134
139
|
end
|
data/lib/hatenablog/version.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
---
|
2
|
+
sources:
|
3
|
+
- name: ruby/gem_rbs_collection
|
4
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
5
|
+
revision: main
|
6
|
+
repo_dir: gems
|
7
|
+
path: ".gem_rbs_collection"
|
8
|
+
gems:
|
9
|
+
- name: erb
|
10
|
+
version: '0'
|
11
|
+
source:
|
12
|
+
type: stdlib
|
13
|
+
- name: net-http
|
14
|
+
version: '0'
|
15
|
+
source:
|
16
|
+
type: stdlib
|
17
|
+
- name: set
|
18
|
+
version: '0'
|
19
|
+
source:
|
20
|
+
type: stdlib
|
21
|
+
- name: time
|
22
|
+
version: '0'
|
23
|
+
source:
|
24
|
+
type: stdlib
|
25
|
+
- name: uri
|
26
|
+
version: '0'
|
27
|
+
source:
|
28
|
+
type: stdlib
|
29
|
+
- name: nokogiri
|
30
|
+
version: '1.11'
|
31
|
+
source:
|
32
|
+
type: git
|
33
|
+
name: ruby/gem_rbs_collection
|
34
|
+
revision: 88e86e0b67262f9ab6244a356e81dd9ca8c55b37
|
35
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
36
|
+
repo_dir: gems
|
data/rbs_collection.yaml
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Download sources
|
2
|
+
sources:
|
3
|
+
- name: ruby/gem_rbs_collection
|
4
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
5
|
+
revision: main
|
6
|
+
repo_dir: gems
|
7
|
+
|
8
|
+
# A directory to install the downloaded RBSs
|
9
|
+
path: .gem_rbs_collection
|
10
|
+
|
11
|
+
gems:
|
12
|
+
# stdlibs
|
13
|
+
- name: erb
|
14
|
+
- name: net-http
|
15
|
+
- name: set
|
16
|
+
- name: time
|
17
|
+
- name: uri
|
18
|
+
|
19
|
+
# gems
|
20
|
+
- name: nokogiri
|
21
|
+
|
22
|
+
# not used
|
23
|
+
- name: activesupport
|
24
|
+
ignore: true
|
25
|
+
- name: ast
|
26
|
+
ignore: true
|
27
|
+
- name: listen
|
28
|
+
ignore: true
|
29
|
+
- name: parallel
|
30
|
+
ignore: true
|
31
|
+
- name: rainbow
|
32
|
+
ignore: true
|
33
|
+
- name: rbs
|
34
|
+
ignore: true
|
35
|
+
- name: steep
|
36
|
+
ignore: true
|
data/sig/hatenablog.rbs
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
module Hatenablog
|
2
|
+
VERSION: String
|
3
|
+
|
4
|
+
class Category
|
5
|
+
@document: Nokogiri::XML::Document
|
6
|
+
@categories: Array[String]
|
7
|
+
@fixed: String
|
8
|
+
|
9
|
+
def self.load_xml: (String xml) -> Category
|
10
|
+
|
11
|
+
def categories: () -> Array[String]
|
12
|
+
def each: () -> Enumerator[untyped, self]
|
13
|
+
| () { (String) -> void } -> Array[String]
|
14
|
+
def fixed?: () -> bool
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def initialize: (String xml) -> void
|
19
|
+
def parse_document: () -> void
|
20
|
+
end
|
21
|
+
|
22
|
+
class Client
|
23
|
+
DEFAULT_CONFIG_PATH: String
|
24
|
+
COLLECTION_URI: String
|
25
|
+
MEMBER_URI: String
|
26
|
+
CATEGORY_URI: String
|
27
|
+
|
28
|
+
@user_id: String
|
29
|
+
@blog_id: String
|
30
|
+
|
31
|
+
attr_writer requester: Requester::Basic | Requester::OAuth
|
32
|
+
|
33
|
+
def self.create: (?String config_file) -> Client
|
34
|
+
| (?String config_file) { (Client) -> void } -> void
|
35
|
+
|
36
|
+
def initialize: (?Configuration config) ?{ (Configuration) -> void } -> void
|
37
|
+
def title: () -> String
|
38
|
+
def author_name: () -> String
|
39
|
+
def entries: (?Integer page) -> Entries
|
40
|
+
def all_entries: () -> Entries
|
41
|
+
def next_feed: (?Feed? feed) -> Feed?
|
42
|
+
def categories: () -> Array[String]
|
43
|
+
def get_entry: (String entry_id) -> Entry
|
44
|
+
def post_entry: (?String title, ?String content, ?Array[String] categories, ?String draft) -> Entry
|
45
|
+
def update_entry: (String entry_id, ?String title, ?String content, ?Array[String] categories, ?String draft, ?String updated) -> Entry
|
46
|
+
def delete_entry: (String entry_id) -> void
|
47
|
+
def collection_uri: (?String user_id, ?String blog_id) -> String
|
48
|
+
def member_uri: (String entry_id, ?String user_id, ?String blog_id) -> String
|
49
|
+
def category_doc_uri: (?String user_id, ?String blog_id) -> String
|
50
|
+
def entry_xml: (?String title, ?String content, ?Array[String] categories, ?String draft, ?String updated, ?String author_name) -> String
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def get: (String uri) -> Net::HTTPResponse
|
55
|
+
def get_collection: (?String uri) -> Net::HTTPResponse
|
56
|
+
def get_category_doc: () -> Net::HTTPResponse
|
57
|
+
def post: (String entry_xml, ?String uri) -> Net::HTTPResponse
|
58
|
+
def put: (String entry_xml, String uri) -> Net::HTTPResponse
|
59
|
+
def delete: (String uri) -> Net::HTTPResponse
|
60
|
+
end
|
61
|
+
|
62
|
+
class Entries
|
63
|
+
include Enumerable[Entry]
|
64
|
+
|
65
|
+
@client: Client
|
66
|
+
@page: Integer
|
67
|
+
@fetch: :partial | :all
|
68
|
+
|
69
|
+
def initialize: (Client client, ?Integer page, ?(:partial | :all) fetch) -> void
|
70
|
+
def each: () -> Enumerator[untyped, self]
|
71
|
+
| () { (Entry) -> void } -> Entries
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def each_all: () { (Entry) -> void } -> Entries
|
76
|
+
def each_partial: () { (Entry) -> void } -> Entries
|
77
|
+
end
|
78
|
+
|
79
|
+
class Configuration < OpenStruct
|
80
|
+
OAUTH_KEYS: [:consumer_key, :consumer_secret, :access_token, :access_token_secret, :user_id, :blog_id]
|
81
|
+
BASIC_KEYS: [:api_key, :user_id, :blog_id]
|
82
|
+
|
83
|
+
def self.create: (String) -> Configuration
|
84
|
+
def check_valid_or_raise: () -> Configuration
|
85
|
+
|
86
|
+
# attribute accessors allowed to define dynamically
|
87
|
+
def consumer_key: () -> untyped # String?
|
88
|
+
def consumer_secret: () -> untyped # String?
|
89
|
+
def access_token: () -> untyped # String?
|
90
|
+
def access_token_secret: () -> untyped # String?
|
91
|
+
def user_id: () -> String
|
92
|
+
def blog_id: () -> String
|
93
|
+
def api_key: () -> untyped # String?
|
94
|
+
def auth_type: () -> untyped # String?
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def lacking_keys: () -> (Array[:consumer_key | :consumer_secret | :access_token | :access_token_secret | :user_id | :blog_id | :api_key | :user_id | :blog_id])
|
99
|
+
end
|
100
|
+
|
101
|
+
class ConfigurationError < StandardError
|
102
|
+
end
|
103
|
+
|
104
|
+
module AfterHook
|
105
|
+
def after_hook: (Symbol hook, *Symbol methods) -> Array[Symbol]
|
106
|
+
|
107
|
+
# methods hooked dynamically
|
108
|
+
def uri=: (String uri) -> untyped
|
109
|
+
def edit_uri=: (String uri) -> untyped
|
110
|
+
def author_name=: (String author_name) -> untyped
|
111
|
+
def title=: (String title) -> untyped
|
112
|
+
def content=: (String content) -> untyped
|
113
|
+
def updated=: (String date) -> untyped
|
114
|
+
def draft=: (String draft) -> untyped
|
115
|
+
def categories=: (Array[String] categories) -> untyped
|
116
|
+
|
117
|
+
# workaround for using `Module` instance methods in `after_hook`
|
118
|
+
def alias_method: (::Symbol | ::String new_name, ::Symbol | ::String old_name) -> ::Symbol
|
119
|
+
def define_method: (Symbol | String arg0, ?Proc | Method | UnboundMethod arg1) -> Symbol
|
120
|
+
| (Symbol | String arg0) { () -> untyped } -> Symbol
|
121
|
+
def instance_methods: (?boolish include_super) -> ::Array[Symbol]
|
122
|
+
end
|
123
|
+
|
124
|
+
class Entry
|
125
|
+
extend AfterHook
|
126
|
+
|
127
|
+
@document: Nokogiri::XML::Document
|
128
|
+
@formatted_content: untyped
|
129
|
+
|
130
|
+
attr_accessor uri: String
|
131
|
+
attr_accessor author_name: String
|
132
|
+
attr_accessor title: String
|
133
|
+
attr_accessor content: String
|
134
|
+
attr_accessor draft: String
|
135
|
+
|
136
|
+
attr_reader edit_uri: String
|
137
|
+
attr_reader id: String?
|
138
|
+
attr_reader updated : Time?
|
139
|
+
|
140
|
+
attr_writer categories: Array[String]
|
141
|
+
|
142
|
+
def self.load_xml: (String xml) -> Entry
|
143
|
+
def self.create: (?uri: String, ?edit_uri: String, ?author_name: String, ?title: String, ?content: String, ?draft: String, ?categories: Array[String], ?updated: String) ?{ (Entry) -> void } -> Entry
|
144
|
+
def self.build_xml: (String uri, String edit_uri, String author_name, String title, String content, String draft, Array[String]? categories, String? updated) -> String
|
145
|
+
|
146
|
+
def updated=: (String date) -> Time?
|
147
|
+
def edit_uri=: (String uri) -> void
|
148
|
+
def draft?: () -> bool
|
149
|
+
def categories: () -> Array[String]
|
150
|
+
def each_category: () { (String) -> void } -> Array[String]
|
151
|
+
def to_xml: () -> String
|
152
|
+
def formatted_content: () -> untyped # result of Nokogiri::XML::NodeSet#[]
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def initialize: (String xml) -> void
|
157
|
+
def parse_document: () -> void
|
158
|
+
def parse_categories: () -> Array[untyped]
|
159
|
+
def update_xml: () -> void
|
160
|
+
def categories_modified?: (Nokogiri::XML::NodeSet old_categories, Array[String] new_categories) -> bool
|
161
|
+
end
|
162
|
+
|
163
|
+
class Feed
|
164
|
+
@document: Nokogiri::XML::Document
|
165
|
+
@entries: Array[Entry]
|
166
|
+
|
167
|
+
attr_reader uri: String
|
168
|
+
attr_reader next_uri: String
|
169
|
+
attr_reader title: String
|
170
|
+
attr_reader author_name: String
|
171
|
+
attr_reader updated: Time
|
172
|
+
|
173
|
+
def self.load_xml: (String xml) -> Feed
|
174
|
+
|
175
|
+
def entries: () -> Array[Entry]
|
176
|
+
def each_entry: () { (Entry) -> void } -> Array[Entry]
|
177
|
+
def has_next?: () -> bool
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def initialize: (String xml) -> void
|
182
|
+
def parse_document: () -> void
|
183
|
+
def parse_entry: () -> void
|
184
|
+
end
|
185
|
+
|
186
|
+
module Requester
|
187
|
+
ATOM_CONTENT_TYPE: String
|
188
|
+
DEFAULT_HEADER: Hash[String, String]
|
189
|
+
|
190
|
+
def self.create: (Configuration config) -> (Basic | OAuth)
|
191
|
+
|
192
|
+
class RequestError < StandardError
|
193
|
+
end
|
194
|
+
|
195
|
+
class OAuth
|
196
|
+
@access_token: ::OAuth::AccessToken
|
197
|
+
|
198
|
+
def initialize: (::OAuth::AccessToken access_token) -> void
|
199
|
+
def get: (String uri) -> Net::HTTPResponse
|
200
|
+
def post: (String uri, ?String body, ?Hash[String, String] headers) -> Net::HTTPResponse
|
201
|
+
def put: (String uri, ?String body, ?Hash[String, String] headers) -> Net::HTTPResponse
|
202
|
+
def delete: (String uri, ?Hash[String, String] headers) -> Net::HTTPResponse
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
def request: (:get | :post | :put | :delete method, String uri, ?body: String?, ?headers: Hash[String, String]?) -> Net::HTTPResponse
|
207
|
+
end
|
208
|
+
|
209
|
+
class Basic
|
210
|
+
METHODS: {get: singleton(Net::HTTP::Get), post: singleton(Net::HTTP::Post), put: singleton(Net::HTTP::Put), delete: singleton(Net::HTTP::Delete)}
|
211
|
+
|
212
|
+
@user_id: String
|
213
|
+
@api_key: String
|
214
|
+
|
215
|
+
def initialize: (String user_id, String api_key) -> void
|
216
|
+
def get: (String uri) -> Net::HTTPResponse
|
217
|
+
def post: (String uri, String body, ?Hash[String, String] headers) -> Net::HTTPResponse
|
218
|
+
def put: (String uri, String body, ?Hash[String, String] headers) -> Net::HTTPResponse
|
219
|
+
def delete: (String uri, ?Hash[String, String] headers) -> Net::HTTPResponse
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
def request: (String uri, :get | :post | :put | :delete method, ?body: String?, ?headers: Hash[String, String]) -> Net::HTTPResponse
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# polyfill for ostruct
|
229
|
+
class OpenStruct
|
230
|
+
def initialize: (?Hash[untyped, untyped]? hash) -> OpenStruct
|
231
|
+
def []: (String | Symbol) -> Object
|
232
|
+
def to_h: -> Hash[Symbol, Object]
|
233
|
+
end
|
234
|
+
|
235
|
+
# polyfill for oauth
|
236
|
+
module OAuth
|
237
|
+
class AccessToken
|
238
|
+
def initialize: (untyped, untyped, ?untyped) -> void
|
239
|
+
end
|
240
|
+
|
241
|
+
class Consumer
|
242
|
+
def initialize: (untyped, untyped, ?untyped) -> void
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# polyfill for yaml
|
247
|
+
module YAML
|
248
|
+
def self.load: (String yaml, ?String? filename, ?fallback: bool, ?symbolize_names: bool) -> untyped
|
249
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hatenablog
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kohei Yamamoto
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-11-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -126,19 +126,21 @@ description: Hatenablog AtomPub API library
|
|
126
126
|
email:
|
127
127
|
- kymmt90@gmail.com
|
128
128
|
executables:
|
129
|
-
-
|
129
|
+
- get_hatena_oauth_access_token
|
130
130
|
extensions: []
|
131
131
|
extra_rdoc_files: []
|
132
132
|
files:
|
133
|
+
- ".github/workflows/build.yml"
|
133
134
|
- ".gitignore"
|
134
|
-
-
|
135
|
+
- CHANGELOG.md
|
135
136
|
- Gemfile
|
136
137
|
- LICENSE.txt
|
137
138
|
- README.md
|
138
139
|
- Rakefile
|
140
|
+
- Steepfile
|
139
141
|
- bin/console
|
140
142
|
- bin/setup
|
141
|
-
- exe/
|
143
|
+
- exe/get_hatena_oauth_access_token
|
142
144
|
- hatenablog.gemspec
|
143
145
|
- lib/hatenablog.rb
|
144
146
|
- lib/hatenablog/category.rb
|
@@ -148,11 +150,14 @@ files:
|
|
148
150
|
- lib/hatenablog/feed.rb
|
149
151
|
- lib/hatenablog/requester.rb
|
150
152
|
- lib/hatenablog/version.rb
|
153
|
+
- rbs_collection.lock.yaml
|
154
|
+
- rbs_collection.yaml
|
155
|
+
- sig/hatenablog.rbs
|
151
156
|
homepage: https://github.com/kymmt90/hatenablog
|
152
157
|
licenses:
|
153
158
|
- MIT
|
154
159
|
metadata: {}
|
155
|
-
post_install_message:
|
160
|
+
post_install_message:
|
156
161
|
rdoc_options: []
|
157
162
|
require_paths:
|
158
163
|
- lib
|
@@ -160,16 +165,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
160
165
|
requirements:
|
161
166
|
- - ">="
|
162
167
|
- !ruby/object:Gem::Version
|
163
|
-
version: '2.
|
168
|
+
version: '2.3'
|
164
169
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
170
|
requirements:
|
166
171
|
- - ">="
|
167
172
|
- !ruby/object:Gem::Version
|
168
173
|
version: '0'
|
169
174
|
requirements: []
|
170
|
-
|
171
|
-
|
172
|
-
signing_key:
|
175
|
+
rubygems_version: 3.2.3
|
176
|
+
signing_key:
|
173
177
|
specification_version: 4
|
174
178
|
summary: Hatenablog AtomPub API library
|
175
179
|
test_files: []
|
data/.travis.yml
DELETED