hatenablog 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0b71f66f0d8e827aa61f9c9298682f0a1a9c5de8
4
+ data.tar.gz: 2d52e581da4e418ddae1877409b8826e46491e10
5
+ SHA512:
6
+ metadata.gz: 0f4dbffdfb7822e0ccd725a9a3813638b69fb01037bb098bd839a95c304d40987ec731172f7adb59d020f9e57e5152573a9dd36bcfa81a87c3d248b4ca04aa6a
7
+ data.tar.gz: f18c984318d3d9991ac17dc1c2ede1b44beb42d87638ebecda60a92faefee249b8a14a4e1785245e42ef5d0993884bd3957b0f1d803cd230ac992e5fd97300fe
@@ -0,0 +1,36 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ ## Specific to RubyMotion:
14
+ .dat*
15
+ .repl_history
16
+ build/
17
+
18
+ ## Documentation cache and generated files:
19
+ /.yardoc/
20
+ /_yardoc/
21
+ /doc/
22
+ /rdoc/
23
+
24
+ ## Environment normalisation:
25
+ /.bundle/
26
+ /vendor/bundle
27
+ /lib/bundler/man/
28
+
29
+ # for a library or gem, you might want to ignore these files since the code is
30
+ # intended to run in multiple environments; otherwise, check them in:
31
+ # Gemfile.lock
32
+ # .ruby-version
33
+ # .ruby-gemset
34
+
35
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
36
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hatenablog.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Kohei Yamamoto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,84 @@
1
+ # Hatenablog
2
+
3
+ A library for Hatenablog AtomPub API.
4
+ This library supports following operations through OAuth authorization:
5
+
6
+ - Get blog feeds, entries and categories
7
+ - Post blog entries
8
+ - Update blog entries
9
+ - Delete blog entries
10
+
11
+ ## Installation
12
+
13
+ ### Install the gem
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'hatenablog'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install hatenablog
26
+
27
+ ### Get OAuth keys and tokens
28
+
29
+ You need to set up OAuth 1.0a keys and tokens before using the gem.
30
+
31
+ #### 1. Get consumer key and consumer key secret
32
+
33
+ Access [Hatena application registoration page](http://developer.hatena.ne.jp/) and get your application consumer key.
34
+
35
+ #### 2. Get your access token and access token secret
36
+
37
+ Execute this command:
38
+
39
+ $ get_access_token <your consumer key> <your consumer secret>
40
+ Visit this website and get the PIN: https://www.hatena.com/oauth/authorize?oauth_token=XXXXXXXXXXXXXXXXXXXX
41
+ Enter the PIN: <your PIN> [Enter]
42
+ Access token: <your access token>
43
+ Access token secret: <your access token secret>
44
+
45
+ #### 3. Set up the YAML configuration file
46
+
47
+ The default file name is `conf.yml`:
48
+
49
+ ```yml
50
+ consumer_key: <Hatena application consumer key>
51
+ consumer_secret: <Hatena application consumer secret>
52
+ access_token: <Hatena application access token>
53
+ access_token_secret: <Hatena application access token secret>
54
+ user_id: <Hatena user ID>
55
+ blog_id: <Hatenablog ID>
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ ```ruby
61
+ require 'hatenablog'
62
+
63
+ # Read the OAuth configuration from 'conf.yml'
64
+ Hatenablog.create do |blog|
65
+ # Get each entry's content
66
+ blog.entries.each do |entry|
67
+ puts entry.content
68
+ end
69
+
70
+ # Post new entry
71
+ posted_entry = blog.post_entry('Entry Title',
72
+ 'This is entry contents', # markdown form
73
+ ['Test', 'Programming']) # categories
74
+
75
+ # Update entry
76
+ posted_entry = blog.update_entry(posted_entry.id,
77
+ 'Revised Entry Title',
78
+ posted_entry.content,
79
+ posted_entry.categories)
80
+
81
+ # Delete entry
82
+ blog.delete_entry(posted_entry.id)
83
+ end
84
+ ```
@@ -0,0 +1,10 @@
1
+ # coding: utf-8
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "test"
8
+ t.test_files = Dir["test/*_test.rb"]
9
+ t.verbose = true
10
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "hatenablog"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ # Get the access token and the access token secret
5
+
6
+ require 'oauth'
7
+
8
+ class AccessTokenGetter
9
+ USAGE = 'Usage: ./get_access_token <consumer key> <consumer secret>'
10
+ SITE_URI = 'https://www.hatena.com'
11
+ REQUEST_TOKEN_URI = '/oauth/initiate?scope=read_public%2Cread_private%2Cwrite_public%2Cwrite_private'
12
+ ACCESS_TOKEN_URI = '/oauth/token'
13
+
14
+ def initialize(consumer_key, consumer_secret)
15
+ @consumer_key = consumer_key
16
+ @consumer_secret = consumer_secret
17
+ @consumer = OAuth::Consumer.new(@consumer_key,
18
+ @consumer_secret,
19
+ oauth_callback: 'oob',
20
+ site: SITE_URI,
21
+ request_token_url: REQUEST_TOKEN_URI,
22
+ access_token_url: ACCESS_TOKEN_URI)
23
+ end
24
+
25
+ def get_request_token
26
+ @consumer.get_request_token
27
+ end
28
+
29
+ def get_access_token(request_token, oauth_verifier)
30
+ # Maybe Hatena returns "parameter_rejected"
31
+ # if "oauth_callback" is in the request header.
32
+ # So oauth_callback is deleted from the header.
33
+ @consumer.options.delete(:oauth_callback)
34
+
35
+ begin
36
+ access_token = request_token.get_access_token(oauth_verifier: oauth_verifier)
37
+ rescue => problem
38
+ raise "Fail to get the access token:\n" + problem.request.body
39
+ end
40
+ access_token
41
+ end
42
+
43
+ def self.get_access_token
44
+ if ARGV.size != 2
45
+ warn USAGE
46
+ exit
47
+ end
48
+
49
+ getter = AccessTokenGetter.new(ARGV[0], ARGV[1])
50
+ request_token = getter.get_request_token
51
+ puts "Visit this website and get the PIN: #{request_token.authorize_url}"
52
+ print 'Enter the PIN:'
53
+ pin = (STDIN.readline).chomp
54
+ access_token = getter.get_access_token(request_token, pin)
55
+ puts "Access token: #{access_token.token}"
56
+ puts "Access token secret: #{access_token.secret}"
57
+ end
58
+ end
59
+
60
+ AccessTokenGetter.get_access_token
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hatenablog/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "hatenablog"
8
+ spec.version = Hatenablog::VERSION
9
+ spec.authors = ["Kohei Yamamoto"]
10
+ spec.email = ["kymmt90@gmail.com"]
11
+
12
+ spec.summary = %q{Hatenablog AtomPub API library}
13
+ spec.description = %q{Hatenablog AtomPub API library}
14
+ spec.homepage = "https://github.com/kymmt90/hatenablog"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.10"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+
25
+ spec.add_dependency "oauth"
26
+ end
@@ -0,0 +1,46 @@
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
@@ -0,0 +1,97 @@
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
@@ -0,0 +1,61 @@
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
@@ -0,0 +1,21 @@
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
@@ -0,0 +1,272 @@
1
+ require 'rexml/document'
2
+ require 'oauth'
3
+
4
+ require 'blog_entry'
5
+ require 'blog_feed'
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
@@ -0,0 +1,3 @@
1
+ module Hatenablog
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hatenablog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kohei Yamamoto
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-10-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: oauth
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Hatenablog AtomPub API library
56
+ email:
57
+ - kymmt90@gmail.com
58
+ executables:
59
+ - get_access_token
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - bin/console
69
+ - bin/setup
70
+ - exe/get_access_token
71
+ - hatenablog.gemspec
72
+ - lib/blog_category.rb
73
+ - lib/blog_entry.rb
74
+ - lib/blog_feed.rb
75
+ - lib/configuration.rb
76
+ - lib/hatenablog.rb
77
+ - lib/hatenablog/version.rb
78
+ homepage: https://github.com/kymmt90/hatenablog
79
+ licenses:
80
+ - MIT
81
+ metadata: {}
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 2.4.5.1
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: Hatenablog AtomPub API library
102
+ test_files: []