hatenablog 0.1.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.
@@ -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: []