hatenablog 0.1.0 → 0.2.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/.travis.yml +5 -0
- data/README.md +9 -9
- data/Rakefile +10 -1
- data/hatenablog.gemspec +8 -1
- data/lib/hatenablog.rb +5 -272
- data/lib/hatenablog/category.rb +47 -0
- data/lib/hatenablog/client.rb +272 -0
- data/lib/hatenablog/configuration.rb +31 -0
- data/lib/hatenablog/entry.rb +98 -0
- data/lib/hatenablog/feed.rb +63 -0
- data/lib/hatenablog/version.rb +1 -1
- metadata +84 -11
- data/lib/blog_category.rb +0 -46
- data/lib/blog_entry.rb +0 -97
- data/lib/blog_feed.rb +0 -61
- data/lib/configuration.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf376e1c7bc165c96b04d31cdca1c7ad519eee6d
|
4
|
+
data.tar.gz: 47dbbe4a2e6e7efff7208a418424a224e74a2ee0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a20f773e2c91639daa1f3c446edcaae4206f83f547ae552381afbd3d6912565b75d87f2dd8cdae3ee7bed4bd773b81f4c97f2319e5041d924a263fc6d8e4d2a
|
7
|
+
data.tar.gz: a839ae31b99284ea6b40e9d736d1375afa90ccd66fcabb872decdb5ad727fd838ee46daaabb507ca702840b2a35b024e0f8cc1d561f18db94b0bf0039051b1a9
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Hatenablog
|
2
2
|
|
3
3
|
A library for Hatenablog AtomPub API.
|
4
|
-
This
|
4
|
+
This gem supports following operations using OAuth authorization:
|
5
5
|
|
6
6
|
- Get blog feeds, entries and categories
|
7
7
|
- Post blog entries
|
@@ -26,7 +26,7 @@ Or install it yourself as:
|
|
26
26
|
|
27
27
|
### Get OAuth keys and tokens
|
28
28
|
|
29
|
-
You need to set up OAuth 1.0a keys and tokens before using
|
29
|
+
You need to set up OAuth 1.0a keys and tokens before using this gem.
|
30
30
|
|
31
31
|
#### 1. Get consumer key and consumer key secret
|
32
32
|
|
@@ -44,7 +44,7 @@ Execute this command:
|
|
44
44
|
|
45
45
|
#### 3. Set up the YAML configuration file
|
46
46
|
|
47
|
-
The default file name is `conf.yml`:
|
47
|
+
The default configuration file name is `conf.yml`:
|
48
48
|
|
49
49
|
```yml
|
50
50
|
consumer_key: <Hatena application consumer key>
|
@@ -61,7 +61,7 @@ blog_id: <Hatenablog ID>
|
|
61
61
|
require 'hatenablog'
|
62
62
|
|
63
63
|
# Read the OAuth configuration from 'conf.yml'
|
64
|
-
Hatenablog.create do |blog|
|
64
|
+
Hatenablog::Client.create do |blog|
|
65
65
|
# Get each entry's content
|
66
66
|
blog.entries.each do |entry|
|
67
67
|
puts entry.content
|
@@ -73,12 +73,12 @@ Hatenablog.create do |blog|
|
|
73
73
|
['Test', 'Programming']) # categories
|
74
74
|
|
75
75
|
# Update entry
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
76
|
+
updated_entry = blog.update_entry(posted_entry.id,
|
77
|
+
'Revised Entry Title',
|
78
|
+
posted_entry.content,
|
79
|
+
posted_entry.categories)
|
80
80
|
|
81
81
|
# Delete entry
|
82
|
-
blog.delete_entry(
|
82
|
+
blog.delete_entry(updated_entry.id)
|
83
83
|
end
|
84
84
|
```
|
data/Rakefile
CHANGED
@@ -2,9 +2,18 @@
|
|
2
2
|
|
3
3
|
require 'bundler/gem_tasks'
|
4
4
|
require 'rake/testtask'
|
5
|
+
require 'yard'
|
6
|
+
require 'yard/rake/yardoc_task'
|
7
|
+
|
8
|
+
task :default => :test
|
5
9
|
|
6
10
|
Rake::TestTask.new do |t|
|
7
11
|
t.libs << "test"
|
8
|
-
t.test_files = Dir["test/*_test.rb"]
|
12
|
+
t.test_files = Dir["test/hatenablog/*_test.rb"]
|
9
13
|
t.verbose = true
|
10
14
|
end
|
15
|
+
|
16
|
+
YARD::Rake::YardocTask.new do |t|
|
17
|
+
t.files = Dir["lib/*.rb"]
|
18
|
+
t.options = %w(--debug --verbose) if $trace
|
19
|
+
end
|
data/hatenablog.gemspec
CHANGED
@@ -19,8 +19,15 @@ 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.0'
|
23
|
+
|
22
24
|
spec.add_development_dependency "bundler", "~> 1.10"
|
23
25
|
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rr", "~> 1.1.2"
|
27
|
+
spec.add_development_dependency "test-unit", "~> 3.1.4"
|
28
|
+
spec.add_development_dependency "test-unit-rr", "~> 1.0.3"
|
29
|
+
spec.add_development_dependency "yard", "~> 0.8.7"
|
24
30
|
|
25
|
-
spec.add_dependency "
|
31
|
+
spec.add_dependency "nokogiri", "~> 1.6.6"
|
32
|
+
spec.add_dependency "oauth", "~> 0.4.7"
|
26
33
|
end
|
data/lib/hatenablog.rb
CHANGED
@@ -1,272 +1,5 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
|
4
|
-
require '
|
5
|
-
require '
|
6
|
-
require 'configuration'
|
7
|
-
|
8
|
-
class Hatenablog
|
9
|
-
DEFAULT_CONFIG_PATH = './config.yml'
|
10
|
-
|
11
|
-
COLLECTION_URI = "https://blog.hatena.ne.jp/%s/%s/atom/entry"
|
12
|
-
MEMBER_URI = "https://blog.hatena.ne.jp/%s/%s/atom/entry/%s"
|
13
|
-
CATEGORY_URI = "https://blog.hatena.ne.jp/%s/%s/atom/category"
|
14
|
-
|
15
|
-
attr_writer :access_token
|
16
|
-
|
17
|
-
# Create a new hatenablog AtomPub client from a configuration file.
|
18
|
-
# @param [String] config_file configuration file path
|
19
|
-
# @return [Hatenablog] created hatenablog client
|
20
|
-
def self.create(config_file = DEFAULT_CONFIG_PATH)
|
21
|
-
config = Configuration.new(config_file)
|
22
|
-
blog = Hatenablog.new(config.consumer_key, config.consumer_secret,
|
23
|
-
config.access_token, config.access_token_secret,
|
24
|
-
config.user_id, config.blog_id)
|
25
|
-
return blog unless block_given?
|
26
|
-
yield blog
|
27
|
-
end
|
28
|
-
|
29
|
-
# Get a blog title.
|
30
|
-
# @return [String] blog title
|
31
|
-
def title
|
32
|
-
feed = BlogFeed.load_xml(get_collection(collection_uri).body)
|
33
|
-
feed.title
|
34
|
-
end
|
35
|
-
|
36
|
-
# Get a author name.
|
37
|
-
# @return [String] blog author name
|
38
|
-
def author_name
|
39
|
-
feed = BlogFeed.load_xml(get_collection(collection_uri).body)
|
40
|
-
feed.author_name
|
41
|
-
end
|
42
|
-
|
43
|
-
# Get blog entries array.
|
44
|
-
# @param [Fixnum] page page number to get
|
45
|
-
# @return [Array] blog entries
|
46
|
-
def entries(page = 0)
|
47
|
-
next_page_uri = collection_uri
|
48
|
-
current_page = 0
|
49
|
-
entries = []
|
50
|
-
while current_page <= page
|
51
|
-
feed = BlogFeed.load_xml(get_collection(next_page_uri).body)
|
52
|
-
entries += feed.entries
|
53
|
-
|
54
|
-
break unless feed.has_next?
|
55
|
-
next_page_uri = feed.next_uri
|
56
|
-
current_page += 1
|
57
|
-
end
|
58
|
-
entries
|
59
|
-
end
|
60
|
-
|
61
|
-
# Get blog categories array.
|
62
|
-
# @return [Array] blog categories
|
63
|
-
def categories
|
64
|
-
categories_doc = REXML::Document.new(get_category_doc.body)
|
65
|
-
categories_list = []
|
66
|
-
categories_doc.elements.each('//atom:category') do |cat|
|
67
|
-
categories_list << cat.attribute('term').to_s
|
68
|
-
end
|
69
|
-
categories_list
|
70
|
-
end
|
71
|
-
|
72
|
-
# Get a blog entry specified by its ID.
|
73
|
-
# @param [String] entry_id entry ID
|
74
|
-
# @return [BlogEntry] entry
|
75
|
-
def get_entry(entry_id)
|
76
|
-
response = get(member_uri(entry_id))
|
77
|
-
BlogEntry.load_xml(response.body)
|
78
|
-
end
|
79
|
-
|
80
|
-
# Post a blog entry.
|
81
|
-
# @param [String] title entry title
|
82
|
-
# @param [String] content entry content
|
83
|
-
# @param [Array] categories entry categories
|
84
|
-
# @param [String] draft this entry is draft if 'yes', otherwise it is not draft
|
85
|
-
# @return [BlogEntry] posted entry
|
86
|
-
def post_entry(title = '', content = '', categories = [], draft = 'no')
|
87
|
-
entry_xml = entry_xml(title, content, categories, draft)
|
88
|
-
response = post(entry_xml)
|
89
|
-
BlogEntry.load_xml(response.body)
|
90
|
-
end
|
91
|
-
|
92
|
-
# Update a blog entry specified by its ID.
|
93
|
-
# @param [String] entry_id updated entry ID
|
94
|
-
# @param [String] title entry title
|
95
|
-
# @param [String] content entry content
|
96
|
-
# @param [Array] categories entry categories
|
97
|
-
# @param [String] draft this entry is draft if 'yes', otherwise it is not draft
|
98
|
-
# @return [BlogEntry] updated entry
|
99
|
-
def update_entry(entry_id, title = '', content = '', categories = [], draft = 'no')
|
100
|
-
entry_xml = entry_xml(title, content, categories, draft)
|
101
|
-
response = put(entry_xml, member_uri(entry_id))
|
102
|
-
BlogEntry.load_xml(response.body)
|
103
|
-
end
|
104
|
-
|
105
|
-
# Delete a blog entry specified by its ID.
|
106
|
-
# @param [String] entry_id deleted entry ID
|
107
|
-
def delete_entry(entry_id)
|
108
|
-
delete(member_uri(entry_id))
|
109
|
-
end
|
110
|
-
|
111
|
-
# Get Hatenablog AtomPub collection URI.
|
112
|
-
# @param [String] user_id Hatena user ID
|
113
|
-
# @param [String] blog_id Hatenablog ID
|
114
|
-
# @return [String] Hatenablog AtomPub collection URI
|
115
|
-
def collection_uri(user_id = @user_id, blog_id = @blog_id)
|
116
|
-
COLLECTION_URI % [user_id, blog_id]
|
117
|
-
end
|
118
|
-
|
119
|
-
# Get Hatenablog AtomPub member URI.
|
120
|
-
# @param [String] entry_id entry ID
|
121
|
-
# @param [String] user_id Hatena user ID
|
122
|
-
# @param [String] blog_id Hatenablog ID
|
123
|
-
# @return [String] Hatenablog AtomPub member URI
|
124
|
-
def member_uri(entry_id, user_id = @user_id, blog_id = @blog_id)
|
125
|
-
MEMBER_URI % [user_id, blog_id, entry_id]
|
126
|
-
end
|
127
|
-
|
128
|
-
# Get Hatenablog AtomPub category document URI.
|
129
|
-
# @param [String] user_id Hatena user ID
|
130
|
-
# @param [String] blog_id Hatenablog ID
|
131
|
-
# @return [String] Hatenablog AtomPub category document URI
|
132
|
-
def category_doc_uri(user_id = @user_id, blog_id = @blog_id)
|
133
|
-
CATEGORY_URI % [user_id, blog_id]
|
134
|
-
end
|
135
|
-
|
136
|
-
# Build a entry XML from arguments.
|
137
|
-
# @param [String] title entry title
|
138
|
-
# @param [String] content entry content
|
139
|
-
# @param [Array] categories entry categories
|
140
|
-
# @param [String] draft this entry is draft if 'yes', otherwise it is not draft
|
141
|
-
# @param [String] author_name entry author name
|
142
|
-
# @return [String] XML string
|
143
|
-
def entry_xml(title = '', content = '', categories = [], draft = 'no', author_name = @user_id)
|
144
|
-
xml = <<XML
|
145
|
-
<?xml version="1.0" encoding="utf-8"?>
|
146
|
-
<entry xmlns="http://www.w3.org/2005/Atom"
|
147
|
-
xmlns:app="http://www.w3.org/2007/app">
|
148
|
-
<title>%s</title>
|
149
|
-
<author><name>%s</name></author>
|
150
|
-
<content type="text/x-markdown">%s</content>
|
151
|
-
%s
|
152
|
-
<app:control>
|
153
|
-
<app:draft>%s</app:draft>
|
154
|
-
</app:control>
|
155
|
-
</entry>
|
156
|
-
XML
|
157
|
-
|
158
|
-
categories_tag = categories.inject('') do |s, c|
|
159
|
-
s + "<category term=\"#{c}\" />\n"
|
160
|
-
end
|
161
|
-
xml % [title, author_name, content, categories_tag, draft]
|
162
|
-
end
|
163
|
-
|
164
|
-
|
165
|
-
private
|
166
|
-
|
167
|
-
def initialize(consumer_key, consumer_secret, access_token, access_token_secret,
|
168
|
-
user_id, blog_id)
|
169
|
-
consumer = OAuth::Consumer.new(consumer_key, consumer_secret)
|
170
|
-
@access_token = OAuthAccessToken.new(OAuth::AccessToken.new(consumer,
|
171
|
-
access_token,
|
172
|
-
access_token_secret))
|
173
|
-
|
174
|
-
@user_id = user_id
|
175
|
-
@blog_id = blog_id
|
176
|
-
end
|
177
|
-
|
178
|
-
|
179
|
-
def get(uri)
|
180
|
-
@access_token.get(uri)
|
181
|
-
end
|
182
|
-
|
183
|
-
def get_collection(uri = collection_uri)
|
184
|
-
unless uri.include?(collection_uri)
|
185
|
-
raise ArgumentError.new('Invalid collection URI: ' + uri)
|
186
|
-
end
|
187
|
-
get(uri)
|
188
|
-
end
|
189
|
-
|
190
|
-
def get_category_doc
|
191
|
-
get(category_doc_uri)
|
192
|
-
end
|
193
|
-
|
194
|
-
def post(entry_xml, uri = collection_uri)
|
195
|
-
@access_token.post(uri, entry_xml)
|
196
|
-
end
|
197
|
-
|
198
|
-
def put(entry_xml, uri)
|
199
|
-
@access_token.put(uri, entry_xml)
|
200
|
-
end
|
201
|
-
|
202
|
-
def delete(uri)
|
203
|
-
@access_token.delete(uri)
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
class OAuthAccessToken
|
208
|
-
|
209
|
-
# Create a new OAuth 1.0a access token.
|
210
|
-
# @param [OAuth::AccessToken] access_token access token object
|
211
|
-
def initialize(access_token)
|
212
|
-
@access_token = access_token
|
213
|
-
end
|
214
|
-
|
215
|
-
# HTTP GET method
|
216
|
-
# @param [string] uri target URI
|
217
|
-
# @return [Net::HTTPResponse] HTTP response
|
218
|
-
def get(uri)
|
219
|
-
begin
|
220
|
-
response = @access_token.get(uri)
|
221
|
-
rescue => problem
|
222
|
-
raise 'Fail to GET: ' + problem.to_s
|
223
|
-
end
|
224
|
-
response
|
225
|
-
end
|
226
|
-
|
227
|
-
# HTTP POST method
|
228
|
-
# @param [string] uri target URI
|
229
|
-
# @param [string] body HTTP request body
|
230
|
-
# @param [string] headers HTTP request headers
|
231
|
-
# @return [Net::HTTPResponse] HTTP response
|
232
|
-
def post(uri,
|
233
|
-
body = '',
|
234
|
-
headers = { 'Content-Type' => 'application/atom+xml; type=entry' } )
|
235
|
-
begin
|
236
|
-
response = @access_token.post(uri, body, headers)
|
237
|
-
rescue => problem
|
238
|
-
raise 'Fail to POST: ' + problem.to_s
|
239
|
-
end
|
240
|
-
response
|
241
|
-
end
|
242
|
-
|
243
|
-
# HTTP PUT method
|
244
|
-
# @param [string] uri target URI
|
245
|
-
# @param [string] body HTTP request body
|
246
|
-
# @param [string] headers HTTP request headers
|
247
|
-
# @return [Net::HTTPResponse] HTTP response
|
248
|
-
def put(uri,
|
249
|
-
body = '',
|
250
|
-
headers = { 'Content-Type' => 'application/atom+xml; type=entry' } )
|
251
|
-
begin
|
252
|
-
response = @access_token.put(uri, body, headers)
|
253
|
-
rescue => problem
|
254
|
-
raise 'Fail to PUT: ' + problem.to_s
|
255
|
-
end
|
256
|
-
response
|
257
|
-
end
|
258
|
-
|
259
|
-
# HTTP DELETE method
|
260
|
-
# @param [string] uri target URI
|
261
|
-
# @param [string] headers HTTP request headers
|
262
|
-
# @return [Net::HTTPResponse] HTTP response
|
263
|
-
def delete(uri,
|
264
|
-
headers = { 'Content-Type' => 'application/atom+xml; type=entry' })
|
265
|
-
begin
|
266
|
-
response = @access_token.delete(uri, headers)
|
267
|
-
rescue => problem
|
268
|
-
raise 'Fail to DELETE: ' + problem.to_s
|
269
|
-
end
|
270
|
-
response
|
271
|
-
end
|
272
|
-
end
|
1
|
+
require 'hatenablog/category'
|
2
|
+
require 'hatenablog/client'
|
3
|
+
require 'hatenablog/configuration'
|
4
|
+
require 'hatenablog/entry'
|
5
|
+
require 'hatenablog/feed'
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Hatenablog
|
4
|
+
class Category
|
5
|
+
|
6
|
+
# Create a new blog categories from a XML string.
|
7
|
+
# @param [String] xml XML string representation
|
8
|
+
# @return [Hatenablog::Category]
|
9
|
+
def self.load_xml(xml)
|
10
|
+
Hatenablog::Category.new(xml)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Array]
|
14
|
+
def categories
|
15
|
+
@categories.dup
|
16
|
+
end
|
17
|
+
|
18
|
+
def each
|
19
|
+
@categories.each do |category|
|
20
|
+
yield category
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# If fixed, only categories in this categories can be used for a blog entry.
|
25
|
+
# @return [Boolean]
|
26
|
+
def fixed?
|
27
|
+
@fixed == 'yes'
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def initialize(xml)
|
34
|
+
@document = Nokogiri::XML(xml)
|
35
|
+
parse_document
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_document
|
39
|
+
@categories = @document.css('atom|category').inject([]) do |categories, category|
|
40
|
+
categories << category['term'].to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
@fixed = @document.at_css('app|categories')['fixed'].to_s
|
44
|
+
@fixed = 'no' if @fixed.nil?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'oauth'
|
2
|
+
|
3
|
+
require 'hatenablog/category'
|
4
|
+
require 'hatenablog/entry'
|
5
|
+
require 'hatenablog/feed'
|
6
|
+
require 'hatenablog/configuration'
|
7
|
+
|
8
|
+
module Hatenablog
|
9
|
+
class Client
|
10
|
+
DEFAULT_CONFIG_PATH = './config.yml'.freeze
|
11
|
+
|
12
|
+
COLLECTION_URI = "https://blog.hatena.ne.jp/%s/%s/atom/entry".freeze
|
13
|
+
MEMBER_URI = "https://blog.hatena.ne.jp/%s/%s/atom/entry/%s".freeze
|
14
|
+
CATEGORY_URI = "https://blog.hatena.ne.jp/%s/%s/atom/category".freeze
|
15
|
+
|
16
|
+
attr_writer :access_token
|
17
|
+
|
18
|
+
# Create a new hatenablog AtomPub client from a configuration file.
|
19
|
+
# @param [String] config_file configuration file path
|
20
|
+
# @return [Hatenablog::Client] created hatenablog client
|
21
|
+
def self.create(config_file = DEFAULT_CONFIG_PATH)
|
22
|
+
config = Configuration.new(config_file)
|
23
|
+
blog = Hatenablog::Client.new(config.consumer_key, config.consumer_secret,
|
24
|
+
config.access_token, config.access_token_secret,
|
25
|
+
config.user_id, config.blog_id)
|
26
|
+
return blog unless block_given?
|
27
|
+
yield blog
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get a blog title.
|
31
|
+
# @return [String] blog title
|
32
|
+
def title
|
33
|
+
feed = Feed.load_xml(get_collection(collection_uri).body)
|
34
|
+
feed.title
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get a author name.
|
38
|
+
# @return [String] blog author name
|
39
|
+
def author_name
|
40
|
+
feed = Feed.load_xml(get_collection(collection_uri).body)
|
41
|
+
feed.author_name
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get blog entries array.
|
45
|
+
# @param [Fixnum] page page number to get
|
46
|
+
# @return [Array] blog entries
|
47
|
+
def entries(page = 0)
|
48
|
+
next_page_uri = collection_uri
|
49
|
+
current_page = 0
|
50
|
+
entries = []
|
51
|
+
while current_page <= page
|
52
|
+
feed = Feed.load_xml(get_collection(next_page_uri).body)
|
53
|
+
entries += feed.entries
|
54
|
+
|
55
|
+
break unless feed.has_next?
|
56
|
+
next_page_uri = feed.next_uri
|
57
|
+
current_page += 1
|
58
|
+
end
|
59
|
+
entries
|
60
|
+
end
|
61
|
+
|
62
|
+
# Get blog categories array.
|
63
|
+
# @return [Array] blog categories
|
64
|
+
def categories
|
65
|
+
categories_doc = Category.new(get_category_doc.body)
|
66
|
+
categories_doc.categories
|
67
|
+
end
|
68
|
+
|
69
|
+
# Get a blog entry specified by its ID.
|
70
|
+
# @param [String] entry_id entry ID
|
71
|
+
# @return [Hatenablog::BlogEntry] entry
|
72
|
+
def get_entry(entry_id)
|
73
|
+
response = get(member_uri(entry_id))
|
74
|
+
Entry.load_xml(response.body)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Post a blog entry.
|
78
|
+
# @param [String] title entry title
|
79
|
+
# @param [String] content entry content
|
80
|
+
# @param [Array] categories entry categories
|
81
|
+
# @param [String] draft this entry is draft if 'yes', otherwise it is not draft
|
82
|
+
# @return [Hatenablog::BlogEntry] posted entry
|
83
|
+
def post_entry(title = '', content = '', categories = [], draft = 'no')
|
84
|
+
entry_xml = entry_xml(title, content, categories, draft)
|
85
|
+
response = post(entry_xml)
|
86
|
+
Entry.load_xml(response.body)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Update a blog entry specified by its ID.
|
90
|
+
# @param [String] entry_id updated entry ID
|
91
|
+
# @param [String] title entry title
|
92
|
+
# @param [String] content entry content
|
93
|
+
# @param [Array] categories entry categories
|
94
|
+
# @param [String] draft this entry is draft if 'yes', otherwise it is not draft
|
95
|
+
# @return [Hatenablog::BlogEntry] updated entry
|
96
|
+
def update_entry(entry_id, title = '', content = '', categories = [], draft = 'no')
|
97
|
+
entry_xml = entry_xml(title, content, categories, draft)
|
98
|
+
response = put(entry_xml, member_uri(entry_id))
|
99
|
+
Entry.load_xml(response.body)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Delete a blog entry specified by its ID.
|
103
|
+
# @param [String] entry_id deleted entry ID
|
104
|
+
def delete_entry(entry_id)
|
105
|
+
delete(member_uri(entry_id))
|
106
|
+
end
|
107
|
+
|
108
|
+
# Get Hatenablog AtomPub collection URI.
|
109
|
+
# @param [String] user_id Hatena user ID
|
110
|
+
# @param [String] blog_id Hatenablog ID
|
111
|
+
# @return [String] Hatenablog AtomPub collection URI
|
112
|
+
def collection_uri(user_id = @user_id, blog_id = @blog_id)
|
113
|
+
COLLECTION_URI % [user_id, blog_id]
|
114
|
+
end
|
115
|
+
|
116
|
+
# Get Hatenablog AtomPub member URI.
|
117
|
+
# @param [String] entry_id entry ID
|
118
|
+
# @param [String] user_id Hatena user ID
|
119
|
+
# @param [String] blog_id Hatenablog ID
|
120
|
+
# @return [String] Hatenablog AtomPub member URI
|
121
|
+
def member_uri(entry_id, user_id = @user_id, blog_id = @blog_id)
|
122
|
+
MEMBER_URI % [user_id, blog_id, entry_id]
|
123
|
+
end
|
124
|
+
|
125
|
+
# Get Hatenablog AtomPub category document URI.
|
126
|
+
# @param [String] user_id Hatena user ID
|
127
|
+
# @param [String] blog_id Hatenablog ID
|
128
|
+
# @return [String] Hatenablog AtomPub category document URI
|
129
|
+
def category_doc_uri(user_id = @user_id, blog_id = @blog_id)
|
130
|
+
CATEGORY_URI % [user_id, blog_id]
|
131
|
+
end
|
132
|
+
|
133
|
+
# Build a entry XML from arguments.
|
134
|
+
# @param [String] title entry title
|
135
|
+
# @param [String] content entry content
|
136
|
+
# @param [Array] categories entry categories
|
137
|
+
# @param [String] draft this entry is draft if 'yes', otherwise it is not draft
|
138
|
+
# @param [String] author_name entry author name
|
139
|
+
# @return [String] XML string
|
140
|
+
def entry_xml(title = '', content = '', categories = [], draft = 'no', author_name = @user_id)
|
141
|
+
xml = <<XML
|
142
|
+
<?xml version="1.0" encoding="utf-8"?>
|
143
|
+
<entry xmlns="http://www.w3.org/2005/Atom"
|
144
|
+
xmlns:app="http://www.w3.org/2007/app">
|
145
|
+
<title>%s</title>
|
146
|
+
<author><name>%s</name></author>
|
147
|
+
<content type="text/x-markdown">%s</content>
|
148
|
+
%s
|
149
|
+
<app:control>
|
150
|
+
<app:draft>%s</app:draft>
|
151
|
+
</app:control>
|
152
|
+
</entry>
|
153
|
+
XML
|
154
|
+
|
155
|
+
categories_tag = categories.inject('') do |s, c|
|
156
|
+
s + "<category term=\"#{c}\" />\n"
|
157
|
+
end
|
158
|
+
xml % [title, author_name, content, categories_tag, draft]
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def initialize(consumer_key, consumer_secret, access_token, access_token_secret,
|
165
|
+
user_id, blog_id)
|
166
|
+
consumer = OAuth::Consumer.new(consumer_key, consumer_secret)
|
167
|
+
@access_token = OAuthAccessToken.new(OAuth::AccessToken.new(consumer,
|
168
|
+
access_token,
|
169
|
+
access_token_secret))
|
170
|
+
|
171
|
+
@user_id = user_id
|
172
|
+
@blog_id = blog_id
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
def get(uri)
|
177
|
+
@access_token.get(uri)
|
178
|
+
end
|
179
|
+
|
180
|
+
def get_collection(uri = collection_uri)
|
181
|
+
unless uri.include?(collection_uri)
|
182
|
+
raise ArgumentError.new('Invalid collection URI: ' + uri)
|
183
|
+
end
|
184
|
+
get(uri)
|
185
|
+
end
|
186
|
+
|
187
|
+
def get_category_doc
|
188
|
+
get(category_doc_uri)
|
189
|
+
end
|
190
|
+
|
191
|
+
def post(entry_xml, uri = collection_uri)
|
192
|
+
@access_token.post(uri, entry_xml)
|
193
|
+
end
|
194
|
+
|
195
|
+
def put(entry_xml, uri)
|
196
|
+
@access_token.put(uri, entry_xml)
|
197
|
+
end
|
198
|
+
|
199
|
+
def delete(uri)
|
200
|
+
@access_token.delete(uri)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class OAuthAccessToken
|
205
|
+
|
206
|
+
# Create a new OAuth 1.0a access token.
|
207
|
+
# @param [OAuth::AccessToken] access_token access token object
|
208
|
+
def initialize(access_token)
|
209
|
+
@access_token = access_token
|
210
|
+
end
|
211
|
+
|
212
|
+
# HTTP GET method
|
213
|
+
# @param [string] uri target URI
|
214
|
+
# @return [Net::HTTPResponse] HTTP response
|
215
|
+
def get(uri)
|
216
|
+
begin
|
217
|
+
response = @access_token.get(uri)
|
218
|
+
rescue => problem
|
219
|
+
raise OAuthError, 'Fail to GET: ' + problem.to_s
|
220
|
+
end
|
221
|
+
response
|
222
|
+
end
|
223
|
+
|
224
|
+
# HTTP POST method
|
225
|
+
# @param [string] uri target URI
|
226
|
+
# @param [string] body HTTP request body
|
227
|
+
# @param [string] headers HTTP request headers
|
228
|
+
# @return [Net::HTTPResponse] HTTP response
|
229
|
+
def post(uri,
|
230
|
+
body = '',
|
231
|
+
headers = { 'Content-Type' => 'application/atom+xml; type=entry' } )
|
232
|
+
begin
|
233
|
+
response = @access_token.post(uri, body, headers)
|
234
|
+
rescue => problem
|
235
|
+
raise OAuthError, 'Fail to POST: ' + problem.to_s
|
236
|
+
end
|
237
|
+
response
|
238
|
+
end
|
239
|
+
|
240
|
+
# HTTP PUT method
|
241
|
+
# @param [string] uri target URI
|
242
|
+
# @param [string] body HTTP request body
|
243
|
+
# @param [string] headers HTTP request headers
|
244
|
+
# @return [Net::HTTPResponse] HTTP response
|
245
|
+
def put(uri,
|
246
|
+
body = '',
|
247
|
+
headers = { 'Content-Type' => 'application/atom+xml; type=entry' } )
|
248
|
+
begin
|
249
|
+
response = @access_token.put(uri, body, headers)
|
250
|
+
rescue => problem
|
251
|
+
raise OAuthError, 'Fail to PUT: ' + problem.to_s
|
252
|
+
end
|
253
|
+
response
|
254
|
+
end
|
255
|
+
|
256
|
+
# HTTP DELETE method
|
257
|
+
# @param [string] uri target URI
|
258
|
+
# @param [string] headers HTTP request headers
|
259
|
+
# @return [Net::HTTPResponse] HTTP response
|
260
|
+
def delete(uri,
|
261
|
+
headers = { 'Content-Type' => 'application/atom+xml; type=entry' })
|
262
|
+
begin
|
263
|
+
response = @access_token.delete(uri, headers)
|
264
|
+
rescue => problem
|
265
|
+
raise OAuthError, 'Fail to DELETE: ' + problem.to_s
|
266
|
+
end
|
267
|
+
response
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
class OAuthError < StandardError; end
|
272
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Hatenablog
|
4
|
+
class Configuration
|
5
|
+
# For OAuth authorization.
|
6
|
+
attr_reader :consumer_key, :consumer_secret, :access_token, :access_token_secret
|
7
|
+
|
8
|
+
attr_reader :user_id, :blog_id
|
9
|
+
|
10
|
+
# Create a new configuration.
|
11
|
+
# @param [String] config_file configuration file path
|
12
|
+
# @return [Hatenablog::Configuration]
|
13
|
+
def initialize(config_file)
|
14
|
+
config = YAML.load_file(config_file)
|
15
|
+
unless config.has_key?('consumer_key') && config.has_key?('consumer_secret') &&
|
16
|
+
config.has_key?('access_token') && config.has_key?('access_token_secret') &&
|
17
|
+
config.has_key?('user_id') && config.has_key?('blog_id')
|
18
|
+
raise ConfigurationError, 'the configure file is incorrect'
|
19
|
+
end
|
20
|
+
|
21
|
+
@consumer_key = config['consumer_key']
|
22
|
+
@consumer_secret = config['consumer_secret']
|
23
|
+
@access_token = config['access_token']
|
24
|
+
@access_token_secret = config['access_token_secret']
|
25
|
+
@user_id = config['user_id']
|
26
|
+
@blog_id = config['blog_id']
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class ConfigurationError < StandardError; end
|
31
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Hatenablog
|
4
|
+
class Entry
|
5
|
+
attr_reader :uri, :edit_uri, :id, :author_name, :title, :content
|
6
|
+
|
7
|
+
# Create a new blog entry from a XML string.
|
8
|
+
# @param [String] xml XML string representation
|
9
|
+
# @return [Hatenablog::Entry]
|
10
|
+
def self.load_xml(xml)
|
11
|
+
Hatenablog::Entry.new(xml)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Create a new blog entry from arguments.
|
15
|
+
# @param [String] uri entry URI
|
16
|
+
# @param [String] edit_uri entry URI for editing
|
17
|
+
# @param [String] author_name entry author name
|
18
|
+
# @param [String] title entry title
|
19
|
+
# @param [String] content entry content
|
20
|
+
# @param [String] draft this entry is draft if 'yes', otherwise it is not draft
|
21
|
+
# @param [Array] categories categories array
|
22
|
+
# @return [Hatenablog::Entry]
|
23
|
+
def self.create(uri: '', edit_uri: '', author_name: '', title: '',
|
24
|
+
content: '', draft: 'no', categories: [])
|
25
|
+
Hatenablog::Entry.new(self.build_xml(uri, edit_uri, author_name, title,
|
26
|
+
content, draft, categories))
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Boolean]
|
30
|
+
def draft?
|
31
|
+
@draft == 'yes'
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Array]
|
35
|
+
def categories
|
36
|
+
@categories.dup
|
37
|
+
end
|
38
|
+
|
39
|
+
def each_category
|
40
|
+
@categories.each do |category|
|
41
|
+
yield category
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [String]
|
46
|
+
def to_xml
|
47
|
+
@document.to_s.gsub(/\"/, "'")
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def self.build_xml(uri, edit_uri, author_name, title, content, draft, categories)
|
54
|
+
xml = <<XML
|
55
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
56
|
+
<entry xmlns:app='http://www.w3.org/2007/app' xmlns='http://www.w3.org/2005/Atom'>
|
57
|
+
<link href='%s' rel='edit'/>
|
58
|
+
<link href='%s' rel='alternate' type='text/html'/>
|
59
|
+
<author><name>%s</name></author>
|
60
|
+
<title>%s</title>
|
61
|
+
<content type='text/x-markdown'>%s</content>
|
62
|
+
%s
|
63
|
+
<app:control>
|
64
|
+
<app:draft>%s</app:draft>
|
65
|
+
</app:control>
|
66
|
+
</entry>
|
67
|
+
XML
|
68
|
+
|
69
|
+
categories_tag = categories.inject('') do |s, c|
|
70
|
+
s + "<category term=\"#{c}\" />\n"
|
71
|
+
end
|
72
|
+
xml % [edit_uri, uri, author_name, title, content, categories_tag, draft]
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize(xml)
|
76
|
+
@document = Nokogiri::XML(xml)
|
77
|
+
parse_document
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse_document
|
81
|
+
@uri = @document.at_css('link[@rel="alternate"]')['href'].to_s
|
82
|
+
@edit_uri = @document.at_css('link[@rel="edit"]')['href'].to_s
|
83
|
+
@id = @edit_uri.split('/').last
|
84
|
+
@author_name = @document.at_css('author name').content
|
85
|
+
@title = @document.at_css('title').content
|
86
|
+
@content = @document.at_css('content').content
|
87
|
+
@draft = @document.at_css('entry app|control app|draft').content
|
88
|
+
@categories = parse_categories
|
89
|
+
end
|
90
|
+
|
91
|
+
def parse_categories
|
92
|
+
categories = @document.css('category').inject([]) do |categories, category|
|
93
|
+
categories << category['term'].to_s
|
94
|
+
end
|
95
|
+
categories
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
require 'hatenablog/entry'
|
5
|
+
|
6
|
+
module Hatenablog
|
7
|
+
class Feed
|
8
|
+
attr_reader :uri, :next_uri, :title, :author_name, :updated
|
9
|
+
|
10
|
+
# Create a new blog feed from a XML string.
|
11
|
+
# @param [String] xml XML string representation
|
12
|
+
# @return [Hatenablog::Feed]
|
13
|
+
def self.load_xml(xml)
|
14
|
+
Hatenablog::Feed.new(xml)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Array]
|
18
|
+
def entries
|
19
|
+
@entries.dup
|
20
|
+
end
|
21
|
+
|
22
|
+
def each_entry
|
23
|
+
@entries.each do |entry|
|
24
|
+
yield entry
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return true if this feed has next feed.
|
29
|
+
# @return [Boolean]
|
30
|
+
def has_next?
|
31
|
+
@next_uri != ''
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def initialize(xml)
|
38
|
+
@document = Nokogiri::XML(xml)
|
39
|
+
parse_document
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_document
|
43
|
+
@uri = @document.at_css("feed link[@rel='alternate']")['href'].to_s
|
44
|
+
@next_uri = if @document.css("feed link[@rel='next']").empty?
|
45
|
+
''
|
46
|
+
else
|
47
|
+
@document.at_css("feed link[@rel='next']")['href'].to_s
|
48
|
+
end
|
49
|
+
@title = @document.at_css('feed title').content
|
50
|
+
@author_name = @document.at_css('author name').content
|
51
|
+
@updated = Time.parse(@document.at_css('feed updated').content)
|
52
|
+
parse_entry
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse_entry
|
56
|
+
@entries = @document.css('feed > entry').inject([]) do |entries, entry|
|
57
|
+
# add namespace 'app' to recognize XML correctly
|
58
|
+
entry['xmlns:app'] = 'http://www.w3.org/2007/app'
|
59
|
+
entries << Hatenablog::Entry.load_xml(entry.to_s)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/hatenablog/version.rb
CHANGED
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.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kohei Yamamoto
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,20 +38,90 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rr
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.1.2
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.1.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: test-unit
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.1.4
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.1.4
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: test-unit-rr
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.0.3
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.0.3
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: yard
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.8.7
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.8.7
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: nokogiri
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.6.6
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.6.6
|
41
111
|
- !ruby/object:Gem::Dependency
|
42
112
|
name: oauth
|
43
113
|
requirement: !ruby/object:Gem::Requirement
|
44
114
|
requirements:
|
45
|
-
- - "
|
115
|
+
- - "~>"
|
46
116
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
117
|
+
version: 0.4.7
|
48
118
|
type: :runtime
|
49
119
|
prerelease: false
|
50
120
|
version_requirements: !ruby/object:Gem::Requirement
|
51
121
|
requirements:
|
52
|
-
- - "
|
122
|
+
- - "~>"
|
53
123
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
124
|
+
version: 0.4.7
|
55
125
|
description: Hatenablog AtomPub API library
|
56
126
|
email:
|
57
127
|
- kymmt90@gmail.com
|
@@ -61,6 +131,7 @@ extensions: []
|
|
61
131
|
extra_rdoc_files: []
|
62
132
|
files:
|
63
133
|
- ".gitignore"
|
134
|
+
- ".travis.yml"
|
64
135
|
- Gemfile
|
65
136
|
- LICENSE.txt
|
66
137
|
- README.md
|
@@ -69,11 +140,12 @@ files:
|
|
69
140
|
- bin/setup
|
70
141
|
- exe/get_access_token
|
71
142
|
- hatenablog.gemspec
|
72
|
-
- lib/blog_category.rb
|
73
|
-
- lib/blog_entry.rb
|
74
|
-
- lib/blog_feed.rb
|
75
|
-
- lib/configuration.rb
|
76
143
|
- lib/hatenablog.rb
|
144
|
+
- lib/hatenablog/category.rb
|
145
|
+
- lib/hatenablog/client.rb
|
146
|
+
- lib/hatenablog/configuration.rb
|
147
|
+
- lib/hatenablog/entry.rb
|
148
|
+
- lib/hatenablog/feed.rb
|
77
149
|
- lib/hatenablog/version.rb
|
78
150
|
homepage: https://github.com/kymmt90/hatenablog
|
79
151
|
licenses:
|
@@ -87,7 +159,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
159
|
requirements:
|
88
160
|
- - ">="
|
89
161
|
- !ruby/object:Gem::Version
|
90
|
-
version: '0'
|
162
|
+
version: '2.0'
|
91
163
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
164
|
requirements:
|
93
165
|
- - ">="
|
@@ -100,3 +172,4 @@ signing_key:
|
|
100
172
|
specification_version: 4
|
101
173
|
summary: Hatenablog AtomPub API library
|
102
174
|
test_files: []
|
175
|
+
has_rdoc:
|
data/lib/blog_category.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
require 'rexml/document'
|
2
|
-
|
3
|
-
class BlogCategory
|
4
|
-
|
5
|
-
# Create a new blog categories from a XML string.
|
6
|
-
# @param [String] xml XML string representation
|
7
|
-
# @return [BlogCategory]
|
8
|
-
def self.load_xml(xml)
|
9
|
-
BlogCategory.new(xml)
|
10
|
-
end
|
11
|
-
|
12
|
-
# @return [Array]
|
13
|
-
def categories
|
14
|
-
@categories.dup
|
15
|
-
end
|
16
|
-
|
17
|
-
def each
|
18
|
-
@categories.each do |category|
|
19
|
-
yield category
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
# If fixed, only categories in this categories can be used for a blog entry.
|
24
|
-
# @return [Boolean]
|
25
|
-
def fixed?
|
26
|
-
@fixed == 'yes'
|
27
|
-
end
|
28
|
-
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def initialize(xml)
|
33
|
-
@document = REXML::Document.new(xml)
|
34
|
-
parse_document
|
35
|
-
end
|
36
|
-
|
37
|
-
def parse_document
|
38
|
-
@categories = []
|
39
|
-
@document.each_element("//atom:category") do |category|
|
40
|
-
@categories << category.attribute('term').to_s
|
41
|
-
end
|
42
|
-
|
43
|
-
@fixed = @document.elements["/app:categories"].attribute('fixed').to_s
|
44
|
-
@fixed = 'no' if @fixed.nil?
|
45
|
-
end
|
46
|
-
end
|
data/lib/blog_entry.rb
DELETED
@@ -1,97 +0,0 @@
|
|
1
|
-
require 'rexml/document'
|
2
|
-
|
3
|
-
class BlogEntry
|
4
|
-
attr_reader :uri, :edit_uri, :id, :author_name, :title, :content
|
5
|
-
|
6
|
-
# Create a new blog entry from a XML string.
|
7
|
-
# @param [String] xml XML string representation
|
8
|
-
# @return [BlogEntry]
|
9
|
-
def self.load_xml(xml)
|
10
|
-
BlogEntry.new(xml)
|
11
|
-
end
|
12
|
-
|
13
|
-
# Create a new blog entry from arguments.
|
14
|
-
# @param [String] uri entry URI
|
15
|
-
# @param [String] edit_uri entry URI for editing
|
16
|
-
# @param [String] author_name entry author name
|
17
|
-
# @param [String] title entry title
|
18
|
-
# @param [String] content entry content
|
19
|
-
# @param [String] draft this entry is draft if 'yes', otherwise it is not draft
|
20
|
-
# @param [Array] categories categories array
|
21
|
-
# @return [BlogEntry]
|
22
|
-
def self.create(uri: '', edit_uri: '', author_name: '', title: '',
|
23
|
-
content: '', draft: 'no', categories: [])
|
24
|
-
BlogEntry.new(self.build_xml(uri, edit_uri, author_name, title,
|
25
|
-
content, draft, categories))
|
26
|
-
end
|
27
|
-
|
28
|
-
# @return [Boolean]
|
29
|
-
def draft?
|
30
|
-
@draft == 'yes'
|
31
|
-
end
|
32
|
-
|
33
|
-
# @return [Array]
|
34
|
-
def categories
|
35
|
-
@categories.dup
|
36
|
-
end
|
37
|
-
|
38
|
-
def each_category
|
39
|
-
@categories.each do |category|
|
40
|
-
yield category
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# @return [String]
|
45
|
-
def to_xml
|
46
|
-
@document.to_s
|
47
|
-
end
|
48
|
-
|
49
|
-
|
50
|
-
private
|
51
|
-
|
52
|
-
def self.build_xml(uri, edit_uri, author_name, title, content, draft, categories)
|
53
|
-
xml = <<XML
|
54
|
-
<?xml version='1.0' encoding='UTF-8'?>
|
55
|
-
<entry xmlns:app='http://www.w3.org/2007/app' xmlns='http://www.w3.org/2005/Atom'>
|
56
|
-
<link href='%s' rel='edit'/>
|
57
|
-
<link href='%s' rel='alternate' type='text/html'/>
|
58
|
-
<author><name>%s</name></author>
|
59
|
-
<title>%s</title>
|
60
|
-
<content type='text/x-markdown'>%s</content>
|
61
|
-
%s
|
62
|
-
<app:control>
|
63
|
-
<app:draft>%s</app:draft>
|
64
|
-
</app:control>
|
65
|
-
</entry>
|
66
|
-
XML
|
67
|
-
|
68
|
-
categories_tag = categories.inject('') do |s, c|
|
69
|
-
s + "<category term=\"#{c}\" />\n"
|
70
|
-
end
|
71
|
-
xml % [edit_uri, uri, author_name, title, content, categories_tag, draft]
|
72
|
-
end
|
73
|
-
|
74
|
-
def initialize(xml)
|
75
|
-
@document = REXML::Document.new(xml)
|
76
|
-
parse_document
|
77
|
-
end
|
78
|
-
|
79
|
-
def parse_document
|
80
|
-
@uri = @document.elements["//link[@rel='alternate']"].attribute('href').to_s
|
81
|
-
@edit_uri = @document.elements["//link[@rel='edit']"].attribute('href').to_s
|
82
|
-
@id = @edit_uri.split('/').last
|
83
|
-
@author_name = @document.elements["//author/name"].text
|
84
|
-
@title = @document.elements["//title"].text
|
85
|
-
@content = @document.elements["//content"].text
|
86
|
-
@draft = @document.elements["//app:draft"].text
|
87
|
-
@categories = parse_categories
|
88
|
-
end
|
89
|
-
|
90
|
-
def parse_categories
|
91
|
-
categories = []
|
92
|
-
@document.each_element("//category") do |category|
|
93
|
-
categories << category.attribute('term').to_s
|
94
|
-
end
|
95
|
-
categories
|
96
|
-
end
|
97
|
-
end
|
data/lib/blog_feed.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
require 'blog_entry'
|
2
|
-
require 'rexml/document'
|
3
|
-
require 'time'
|
4
|
-
|
5
|
-
class BlogFeed
|
6
|
-
attr_reader :uri, :next_uri, :title, :author_name, :updated
|
7
|
-
|
8
|
-
# Create a new blog feed from a XML string.
|
9
|
-
# @param [String] xml XML string representation
|
10
|
-
# @return [BlogFeed]
|
11
|
-
def self.load_xml(xml)
|
12
|
-
BlogFeed.new(xml)
|
13
|
-
end
|
14
|
-
|
15
|
-
# @return [Array]
|
16
|
-
def entries
|
17
|
-
@entries.dup
|
18
|
-
end
|
19
|
-
|
20
|
-
def each_entry
|
21
|
-
@entries.each do |entry|
|
22
|
-
yield entry
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Return true if this feed has next feed.
|
27
|
-
# @return [Boolean]
|
28
|
-
def has_next?
|
29
|
-
@next_uri != ''
|
30
|
-
end
|
31
|
-
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def initialize(xml)
|
36
|
-
@document = REXML::Document.new(xml)
|
37
|
-
parse_document
|
38
|
-
end
|
39
|
-
|
40
|
-
def parse_document
|
41
|
-
@uri = @document.elements["/feed/link[@rel='alternate']"].attribute('href').to_s
|
42
|
-
@next_uri = if @document.elements["/feed/link[@rel='next']"].nil?
|
43
|
-
''
|
44
|
-
else
|
45
|
-
@document.elements["/feed/link[@rel='next']"].attribute('href').to_s
|
46
|
-
end
|
47
|
-
@title = @document.elements["/feed/title"].text
|
48
|
-
@author_name = @document.elements["//author/name"].text
|
49
|
-
@updated = Time.parse(@document.elements["/feed/updated"].text)
|
50
|
-
parse_entry
|
51
|
-
end
|
52
|
-
|
53
|
-
def parse_entry
|
54
|
-
@entries = []
|
55
|
-
@document.elements.collect("//entry") do |entry|
|
56
|
-
# add namespace 'app' to recognize XML correctly
|
57
|
-
entry.add_attribute('xmlns:app', 'http://www.w3.org/2007/app')
|
58
|
-
@entries << BlogEntry.load_xml(entry.to_s)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
data/lib/configuration.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
|
3
|
-
class Configuration
|
4
|
-
# For OAuth authorization.
|
5
|
-
attr_reader :consumer_key, :consumer_secret, :access_token, :access_token_secret
|
6
|
-
|
7
|
-
attr_reader :user_id, :blog_id
|
8
|
-
|
9
|
-
# Create a new configuration.
|
10
|
-
# @param [String] config_file configuration file path
|
11
|
-
# @return [Configuration]
|
12
|
-
def initialize(config_file)
|
13
|
-
config = YAML.load_file(config_file)
|
14
|
-
@consumer_key = config['consumer_key']
|
15
|
-
@consumer_secret = config['consumer_secret']
|
16
|
-
@access_token = config['access_token']
|
17
|
-
@access_token_secret = config['access_token_secret']
|
18
|
-
@user_id = config['user_id']
|
19
|
-
@blog_id = config['blog_id']
|
20
|
-
end
|
21
|
-
end
|