hatenabm 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.
- data/ChangeLog +6 -0
- data/README +53 -0
- data/lib/hatenabm.rb +160 -0
- data/test/test_hatenabm.rb +75 -0
- metadata +44 -0
data/ChangeLog
ADDED
data/README
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
= HatenaBM
|
2
|
+
|
3
|
+
HatenaBM is Hatena Bookmark (Japanese Social Bookmark Service) binding for Ruby.
|
4
|
+
|
5
|
+
Hatena Bookmark : http://b.hatena.ne.jp
|
6
|
+
Hatena Bookmark API Reference (Japanese) : http://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA%A5%D6%A5%C3%A5%AF%A5%DE%A1%BC%A5%AFAtomAPI
|
7
|
+
|
8
|
+
== Installation
|
9
|
+
|
10
|
+
$ sudo gem install hatenabm
|
11
|
+
|
12
|
+
== Usage
|
13
|
+
|
14
|
+
require 'rubygems'
|
15
|
+
require 'hatenabm'
|
16
|
+
require 'pp'
|
17
|
+
|
18
|
+
# initialize
|
19
|
+
hbm = HatenaBM.new(
|
20
|
+
:user => "username",
|
21
|
+
:pass => "password"
|
22
|
+
)
|
23
|
+
|
24
|
+
# post new bookmark
|
25
|
+
hbm.post(
|
26
|
+
:title => "bookmark's title", # title
|
27
|
+
:link => "http://www.example.com", # url
|
28
|
+
:tags => "foo bar", # tags (separate space)
|
29
|
+
:summary => "this is example post." # description of this bookmark
|
30
|
+
)
|
31
|
+
|
32
|
+
# show recent bookmarks (Atom Format)
|
33
|
+
pp hbm.recent
|
34
|
+
|
35
|
+
# show specified bookmark
|
36
|
+
pp hbm.get(:eid => "4211817") # eid - bookmark's id
|
37
|
+
|
38
|
+
# modify posted bookmark
|
39
|
+
hbm.modify(
|
40
|
+
:eid => "421817", # eid
|
41
|
+
:tags => "bar com", # tags (separate space)
|
42
|
+
:summary => "modify bookmark's description"
|
43
|
+
)
|
44
|
+
|
45
|
+
# delete specified bookmark
|
46
|
+
hbm.delete(:eid => "421817") # eid
|
47
|
+
|
48
|
+
== Author
|
49
|
+
- drawnboy ( http://d.hatena.ne.jp/drawnboy ) <drawn.boy@gmail.com.nospam>
|
50
|
+
- s-tanaka ( http://d.hatena.ne.jp/ha-tan )is written by get_wsse method
|
51
|
+
- gorou ( http://rails2u.com )
|
52
|
+
|
53
|
+
License:: 2-clause BSD Lisence
|
data/lib/hatenabm.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'time'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
# if using ruby 1.9.0 earlier, define HTTP DELETE Method
|
7
|
+
if RUBY_VERSION < '1.9.0'
|
8
|
+
module Net
|
9
|
+
class HTTP
|
10
|
+
class Delete < HTTPRequest
|
11
|
+
METHOD = 'DELETE'
|
12
|
+
REQUEST_HAS_BODY = false
|
13
|
+
RESPONSE_HAS_BODY = true
|
14
|
+
end
|
15
|
+
def delete(path,initheader = nil)
|
16
|
+
res = request(Delete.new(path,initheader))
|
17
|
+
res.value unless @newimpl
|
18
|
+
res
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class HatenaBM
|
25
|
+
|
26
|
+
VERSION = "0.0.3"
|
27
|
+
|
28
|
+
HATENA_URL = "b.hatena.ne.jp"
|
29
|
+
FEED_PATH = "/atom/feed"
|
30
|
+
POST_PATH = "/atom/post"
|
31
|
+
EDIT_PATH = "/atom/edit/"
|
32
|
+
|
33
|
+
def initialize(options)
|
34
|
+
user = options[:user]
|
35
|
+
pass = options[:pass]
|
36
|
+
@wsse = get_wsse(user, pass)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Post new bookmark
|
40
|
+
# args: title, link, summary, tags
|
41
|
+
def post(options)
|
42
|
+
title = options[:title]
|
43
|
+
link = options[:link]
|
44
|
+
summary = options[:summary] || nil
|
45
|
+
tags = options[:tags] || nil
|
46
|
+
header = {
|
47
|
+
"Content-Type" => 'application/x.atom+xml; charset="utf-8"',
|
48
|
+
"X-WSSE" => @wsse
|
49
|
+
}
|
50
|
+
tags = "[#{tags.split(/\s/).join('][')}]" unless tags.nil?
|
51
|
+
|
52
|
+
data =<<-EOF
|
53
|
+
<?xml version="1.0"?>
|
54
|
+
<entry xmlns="http://purl.org/atom/ns#">
|
55
|
+
<title>#{title}</title>
|
56
|
+
<link rel="related" type="text/html" href="#{link}" />
|
57
|
+
<summary type="text/plain">#{tags}#{summary}</summary>
|
58
|
+
</entry>
|
59
|
+
EOF
|
60
|
+
|
61
|
+
Net::HTTP.version_1_2
|
62
|
+
Net::HTTP.start(HATENA_URL){|http|
|
63
|
+
response = http.post(POST_PATH, toutf8(data), header)
|
64
|
+
raise "Post Error : #{response.code} - #{response.message}" unless response.code == "201"
|
65
|
+
return true
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
# Get recent bookmarks
|
70
|
+
def recent
|
71
|
+
header = { "X-WSSE" => @wsse }
|
72
|
+
Net::HTTP.version_1_2
|
73
|
+
Net::HTTP.start(HATENA_URL){|http|
|
74
|
+
response = http.get(FEED_PATH, header)
|
75
|
+
return response.body
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
# Get specified bookmark
|
80
|
+
# args : eid
|
81
|
+
def get(options)
|
82
|
+
eid = options[:eid] || nil
|
83
|
+
raise "Get Error : Invalid eid" if eid.nil?
|
84
|
+
api_path = EDIT_PATH + eid
|
85
|
+
header = { "X-WSSE"=> @wsse }
|
86
|
+
|
87
|
+
Net::HTTP.version_1_2
|
88
|
+
Net::HTTP.start(HATENA_URL){|http|
|
89
|
+
response = http.get(api_path, header)
|
90
|
+
return response.body
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
# Modify specified bookmark
|
95
|
+
# args: eid, title, tags
|
96
|
+
def modify(options)
|
97
|
+
eid = options[:eid] || nil
|
98
|
+
title = options[:title] || nil
|
99
|
+
tags = options[:tags] || nil
|
100
|
+
summary = options[:summary] || nil
|
101
|
+
|
102
|
+
raise "Edit Error : need eid" if eid.nil?
|
103
|
+
|
104
|
+
api_path = EDIT_PATH + eid
|
105
|
+
header = { "X-WSSE" => @wsse }
|
106
|
+
tags = "[#{tags.split(/\s/).join('][')}]" unless tags.nil?
|
107
|
+
|
108
|
+
data = "<?xml version=\"1.0\"?>"
|
109
|
+
data << "<entry xmlns=\"http://purl.org/atom/ns#\">"
|
110
|
+
data << "<title>#{title}</title>" unless title.nil?
|
111
|
+
data << "<summary type=\"text/plain\">#{tags}#{summary}</summary>" unless summary.nil?
|
112
|
+
data << "</entry>"
|
113
|
+
|
114
|
+
Net::HTTP.version_1_2
|
115
|
+
Net::HTTP.start(HATENA_URL){|http|
|
116
|
+
response = http.put(api_path, toutf8(data), header)
|
117
|
+
raise "Edit Error : #{response.code} - #{response.message}" unless response.code == "200"
|
118
|
+
return true
|
119
|
+
}
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
# Delete specified bookmark
|
124
|
+
# args: eid
|
125
|
+
def delete(options)
|
126
|
+
eid = options[:eid] || nil
|
127
|
+
raise "Delete Error : Invalid eid" if eid.nil?
|
128
|
+
api_path = EDIT_PATH + eid
|
129
|
+
header = { "X-WSSE" => @wsse }
|
130
|
+
|
131
|
+
Net::HTTP.version_1_2
|
132
|
+
Net::HTTP.start(HATENA_URL){|http|
|
133
|
+
response = http.delete(api_path, header)
|
134
|
+
raise "Delete Error : #{response.code} - #{response.message}" unless response.code == "200"
|
135
|
+
return true
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
# Calculate WSSE Header method
|
140
|
+
private
|
141
|
+
def get_wsse(user, pass)
|
142
|
+
created = Time.now.iso8601
|
143
|
+
|
144
|
+
nonce = ''
|
145
|
+
20.times do
|
146
|
+
nonce << rand(256).chr
|
147
|
+
end
|
148
|
+
|
149
|
+
passdigest = Digest::SHA1.digest(nonce + created + pass)
|
150
|
+
|
151
|
+
return "UsernameToken Username=\"#{user}\", " +
|
152
|
+
"PasswordDigest=\"#{Base64.encode64(passdigest).chomp}\", " +
|
153
|
+
"Nonce=\"#{Base64.encode64(nonce).chomp}\", " +
|
154
|
+
"Created=\"#{created}\""
|
155
|
+
end
|
156
|
+
|
157
|
+
def toutf8(str)
|
158
|
+
Kconv.toutf8(str)
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'hatenabm'
|
3
|
+
|
4
|
+
class TestHatenaBM < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@hatenabm = HatenaBM.new(:user => "username", :pass => "password")
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_recent
|
11
|
+
assert_match(
|
12
|
+
/^<\?xml version=\"1\.0\" encoding=\"utf\-8\"\?>/,
|
13
|
+
@hatenabm.recent
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_post
|
18
|
+
assert_equal(
|
19
|
+
true,
|
20
|
+
@hatenabm.post(
|
21
|
+
:title => "bookmark's title",
|
22
|
+
:link => "http://www.example.com",
|
23
|
+
:tags => "foo bar",
|
24
|
+
:summary => "this is example post."
|
25
|
+
)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_get
|
30
|
+
assert_match(
|
31
|
+
/^<\?xml version=\"1\.0\" encoding=\"utf\-8\"\?>/,
|
32
|
+
@hatenabm.get(:eid => "4211817")
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_modify
|
37
|
+
assert(
|
38
|
+
true,
|
39
|
+
@hatenabm.modify(
|
40
|
+
:eid => "421817",
|
41
|
+
:tags => "bar com",
|
42
|
+
:summary => "modify bookmark's description"
|
43
|
+
)
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_delete
|
48
|
+
assert(
|
49
|
+
true,
|
50
|
+
@hatenabm.delete(:eid => "421817")
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_invalid_access
|
55
|
+
assert_raises(RuntimeError) do
|
56
|
+
@hatenabm.post(:title => "invalid", :summary => "summary")
|
57
|
+
end
|
58
|
+
|
59
|
+
assert_raises(RuntimeError) do
|
60
|
+
@hatenabm.get(:invalide => "421817")
|
61
|
+
end
|
62
|
+
|
63
|
+
assert_raises(RuntimeError) do
|
64
|
+
@hatenabm.modify(:summary => "summary")
|
65
|
+
end
|
66
|
+
|
67
|
+
assert_raises(RuntimeError) do
|
68
|
+
@hatenabm.delete(:invalid => "421817")
|
69
|
+
end
|
70
|
+
|
71
|
+
assert_raises(RuntimeError) do
|
72
|
+
@hatenabm.delete(:eid => "invalid")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
metadata
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.10
|
3
|
+
specification_version: 1
|
4
|
+
name: hatenabm
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2005-10-14
|
8
|
+
summary: Hatena Bookmark AtomAPI binding for Ruby
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: drawn.boy@gmail.com.nospam
|
12
|
+
homepage: http://hatenabm.rubyforge.org
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire: cool
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
-
|
22
|
+
- ">"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.0.0
|
25
|
+
version:
|
26
|
+
platform: ruby
|
27
|
+
authors:
|
28
|
+
- drawnboy
|
29
|
+
files:
|
30
|
+
- test/test_hatenabm.rb
|
31
|
+
- lib/hatenabm.rb
|
32
|
+
- README
|
33
|
+
- ChangeLog
|
34
|
+
test_files:
|
35
|
+
- test/test_hatenabm.rb
|
36
|
+
rdoc_options:
|
37
|
+
- "--main"
|
38
|
+
- README
|
39
|
+
extra_rdoc_files:
|
40
|
+
- README
|
41
|
+
executables: []
|
42
|
+
extensions: []
|
43
|
+
requirements: []
|
44
|
+
dependencies: []
|