hatenabm 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|