pinboard_api 0.1.0 → 0.7.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/.gitignore +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +5 -1
- data/README.md +81 -16
- data/Rakefile +10 -0
- data/lib/core_ext/array.rb +11 -0
- data/lib/core_ext/hash.rb +8 -0
- data/lib/pinboard_api.rb +24 -18
- data/lib/pinboard_api/post.rb +103 -21
- data/lib/pinboard_api/tag.rb +11 -9
- data/lib/pinboard_api/user.rb +1 -1
- data/lib/pinboard_api/version.rb +1 -1
- data/spec/core_ext/array_spec.rb +11 -0
- data/spec/core_ext/hash_spec.rb +18 -0
- data/spec/fixtures/vcr_cassettes/posts/all/custom_count.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/all/custom_tag.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/all/custom_times.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/all/default_values.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/all/not_found.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/dates/custom_tag.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/dates/default_values.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/delete/unsuccessful_class.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/destroy/successful_class.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/destroy/successful_instance.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/destroy/unsuccessful_instance.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/find/found.json +1 -1
- data/spec/fixtures/vcr_cassettes/posts/find/not_found.json +1 -1
- data/spec/fixtures/vcr_cassettes/posts/recent/custom_count.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/recent/custom_tag.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/recent/default_values.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/suggest.json +1 -0
- data/spec/fixtures/vcr_cassettes/posts/update.json +1 -1
- data/spec/fixtures/vcr_cassettes/tags/all.json +1 -1
- data/spec/fixtures/vcr_cassettes/tags/destroy/successful_class.json +1 -0
- data/spec/fixtures/vcr_cassettes/tags/destroy/successful_instance.json +1 -0
- data/spec/fixtures/vcr_cassettes/tags/destroy/unsuccessful_class.json +1 -0
- data/spec/fixtures/vcr_cassettes/tags/destroy/unsuccessful_instance.json +1 -0
- data/spec/fixtures/vcr_cassettes/tags/find/found.json +1 -1
- data/spec/fixtures/vcr_cassettes/tags/find/not_found.json +1 -1
- data/spec/fixtures/vcr_cassettes/tags/rename/successful.json +1 -1
- data/spec/fixtures/vcr_cassettes/tags/rename/unsuccessful.json +1 -1
- data/spec/fixtures/vcr_cassettes/user/secret.json +1 -1
- data/spec/pinboard_api_spec.rb +0 -4
- data/spec/post_spec.rb +358 -29
- data/spec/spec_helper.rb +8 -4
- data/spec/tag_spec.rb +27 -11
- metadata +48 -7
- data/spec/fixtures/vcr_cassettes/tags/delete/successful.json +0 -1
- data/spec/fixtures/vcr_cassettes/tags/delete/unsuccessful.json +0 -1
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# PinboardApi
|
1
|
+
# PinboardApi [](http://travis-ci.org/phlipper/pinboard_api) [](https://codeclimate.com/github/phlipper/pinboard_api)
|
2
2
|
|
3
3
|
## Description
|
4
4
|
|
@@ -9,8 +9,13 @@ This client aims to cover all of the Pinboard [API v1](https://pinboard.in/api/)
|
|
9
9
|
|
10
10
|
## Requirements
|
11
11
|
|
12
|
-
* This library requires Ruby 1.9.2 or newer.
|
13
12
|
* You must have a paid Pinboard account to use the API. It is a great service and you can [signup here](https://pinboard.in/signup/) if you don't already have an account.
|
13
|
+
* Currently tested on the following Ruby versions:
|
14
|
+
* 1.9.2
|
15
|
+
* 1.9.3
|
16
|
+
* JRuby (1.9 mode)
|
17
|
+
|
18
|
+
_Note:_ Specs are currently passing on Rubinius with `RBXOPT=-X19` on my local machine but there is a failing spec on [Travis CI](http://travis-ci.org/#!/phlipper/pinboard_api). I will update the `README` with official support for Rubinus once everything runs smoothly on Travis.
|
14
19
|
|
15
20
|
|
16
21
|
## Installation
|
@@ -34,6 +39,22 @@ $ gem install pinboard_api
|
|
34
39
|
```
|
35
40
|
|
36
41
|
|
42
|
+
## Getting Started
|
43
|
+
|
44
|
+
You will need to set your username and password for the Pinboard service.
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
PinboardApi.username = "phlipper"
|
48
|
+
PinboardApi.password = "[REDACTED]"
|
49
|
+
```
|
50
|
+
|
51
|
+
You may also set the SSL options which will be passed through to [Faraday](https://github.com/technoweenie/faraday#readme):
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
PinboardApi.ssl_options = { ca_file: "/opt/local/share/curl/curl-ca-bundle.crt" }
|
55
|
+
```
|
56
|
+
|
57
|
+
|
37
58
|
## Usage
|
38
59
|
|
39
60
|
The `PinboardApi` namespace implements the 3 primary object types: `Post`, `Tag`, and `User`.
|
@@ -50,24 +71,63 @@ PinboardApi::Post.update
|
|
50
71
|
```
|
51
72
|
|
52
73
|
* ~~[posts/add](https://pinboard.in/api#posts_add) - add a new bookmark~~
|
53
|
-
*
|
74
|
+
* [posts/delete](https://pinboard.in/api#posts_delete) - delete an existing bookmark
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
post = PinboardApi::Post.find(url: "https://pinboard.in/u:phlipper").first
|
78
|
+
post.destroy
|
79
|
+
# => #<PinboardApi::Post:0x007ffcb5166cf0 @description="Pinboard - antisocial bookmarking", @extended="", @hash="bc857ba651d134be0c9a5267e943c3ce", @url="https://pinboard.in/u:phlipper", @meta=nil, @tags="test", @time="2012-07-11T09:16:14Z">
|
80
|
+
|
81
|
+
PinboardApi::Post.destroy("https://pinboard.in/u:phlipper")
|
82
|
+
# => #<PinboardApi::Post:0x007f98d6946d78 @description="Pinboard - antisocial bookmarking", @extended="", @hash="bc857ba651d134be0c9a5267e943c3ce", @url="https://pinboard.in/u:phlipper", @meta=nil, @tags="test", @time="2012-07-11T09:17:36Z">
|
83
|
+
```
|
84
|
+
|
54
85
|
* [posts/get](https://pinboard.in/api#posts_get) - get bookmark for a single date, or fetch specific items by URL
|
55
86
|
|
56
87
|
```ruby
|
57
88
|
PinboardApi::Post.find(tag: "test")
|
58
|
-
# => [#<PinboardApi::Post:0x007fdce4547388 @description="Test.com – Certification Program Management – Create Online Tests with This Authoring, Management, Training and E-Learning Software", @extended="", @hash="dbb720d788ffaeb0afb7572104072f4a", @
|
89
|
+
# => [#<PinboardApi::Post:0x007fdce4547388 @description="Test.com – Certification Program Management – Create Online Tests with This Authoring, Management, Training and E-Learning Software", @extended="", @hash="dbb720d788ffaeb0afb7572104072f4a", @url="http://test.com/", @tags="test junk", @time="2012-07-07T04:18:28Z">, ...]
|
59
90
|
|
60
91
|
PinboardApi::Post.find(hash: "dbb720d788ffaeb0afb7572104072f4a", meta: "yes")
|
61
|
-
# => [#<PinboardApi::Post:0x007fac2b9d6690 @description="Test.com – Certification Program Management – Create Online Tests with This Authoring, Management, Training and E-Learning Software", @extended="", @hash="dbb720d788ffaeb0afb7572104072f4a", @href="http://test.com/", @meta="73b192512e3e4829806f5eee0a6b456d", @tags="test junk", @time="2012-07-07T04:18:28Z">, ...]
|
62
|
-
|
63
92
|
PinboardApi::Post.find(dt: Date.parse("2012-07-07"))
|
64
|
-
# => [#<PinboardApi::Post:0x007fac2ba0fdf0 @description="Test.com – Certification Program Management – Create Online Tests with This Authoring, Management, Training and E-Learning Software", @extended="", @hash="dbb720d788ffaeb0afb7572104072f4a", @href="http://test.com/", @meta=nil, @tags="test junk", @time="2012-07-07T04:18:28Z">, ...]
|
65
93
|
```
|
66
94
|
|
67
|
-
*
|
68
|
-
|
69
|
-
|
70
|
-
|
95
|
+
* [posts/dates](https://pinboard.in/api#posts_dates) - list dates on which bookmarks were posted
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
PinboardApi::Post.dates
|
99
|
+
# => [{"count"=>1, "date"=>#<Date: 2012-07-10 ((2456119j,0s,0n),+0s,2299161j)>}, {"count"=>3, "date"=>#<Date: 2012-07-08 ((2456117j,0s,0n),+0s,2299161j)>}, ...]
|
100
|
+
|
101
|
+
PinboardApi::Post.dates(tag: "ruby")
|
102
|
+
```
|
103
|
+
|
104
|
+
* [posts/recent](https://pinboard.in/api#posts_recent) - fetch recent bookmarks
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
PinboardApi::Post.recent
|
108
|
+
# => [#<PinboardApi::Post:0x007ffe150e1fd0 @description="Techniques to Secure Your Website with Ruby on Rails..."> ...]
|
109
|
+
|
110
|
+
PinboardApi::Post.recent(count: 3)
|
111
|
+
PinboardApi::Post.recent(tag: "ruby")
|
112
|
+
PinboardApi::Post.recent(count: 25, tag: ["ruby", "programming"])
|
113
|
+
```
|
114
|
+
|
115
|
+
* [posts/all](https://pinboard.in/api#posts_all) - fetch all bookmarks by date, tag, or range
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
PinboardApi::Post.all
|
119
|
+
# => [#<PinboardApi::Post:0x007ffe150e1fd0 @description="Techniques to Secure Your Website with Ruby on Rails..."> ...]
|
120
|
+
|
121
|
+
PinboardApi::Post.all(tag: %w[ruby programming], meta: true, results: 30)
|
122
|
+
PinboardApi::Post.all(start: 50, fromdt: 2.weeks.ago, todt: 1.week.ago)
|
123
|
+
```
|
124
|
+
|
125
|
+
* [posts/suggest](https://pinboard.in/api#posts_suggest) - fetch popular and recommended tags for a url
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
PinboardApi::Post.suggest("http://blog.com")
|
129
|
+
# => {"popular"=>["hosting", "blogs", "blog", "free"], "recommended"=>["blog", "blogging", "blogs", "free"]}
|
130
|
+
```
|
71
131
|
|
72
132
|
|
73
133
|
### Tag
|
@@ -86,13 +146,11 @@ PinboardApi::Tag.find("leadership")
|
|
86
146
|
|
87
147
|
```ruby
|
88
148
|
tag = PinboardApi::Tag.find("foo")
|
149
|
+
tag.destroy
|
89
150
|
# => #<PinboardApi::Tag:0x007fdce45f56e0 @name="foo", @count=1>
|
90
151
|
|
91
|
-
|
92
|
-
# => #<PinboardApi::Tag:
|
93
|
-
|
94
|
-
tag = PinboardApi::Tag.find("foo")
|
95
|
-
# => nil
|
152
|
+
PinboardApi::Tag.destroy("foo")
|
153
|
+
# => #<PinboardApi::Tag:0x007fdce45f20f8 @name="foo", @count=1>
|
96
154
|
```
|
97
155
|
|
98
156
|
* [tags/rename](https://pinboard.in/api#tags_rename) - rename a tag
|
@@ -116,6 +174,13 @@ PinboardApi::User.secret
|
|
116
174
|
```
|
117
175
|
|
118
176
|
|
177
|
+
## TODO
|
178
|
+
|
179
|
+
* Implement Post.add/create
|
180
|
+
* Implement support for the new `[auth_token](http://pinboard.in/api/#authentication)`
|
181
|
+
* Cleanup/refactor internal exception handling
|
182
|
+
|
183
|
+
|
119
184
|
## Contributing
|
120
185
|
|
121
186
|
1. Fork it
|
data/Rakefile
CHANGED
data/lib/pinboard_api.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require "faraday"
|
2
2
|
require "faraday_middleware"
|
3
3
|
|
4
|
+
require "core_ext/array"
|
5
|
+
require "core_ext/hash"
|
6
|
+
|
4
7
|
require "pinboard_api/post"
|
5
8
|
require "pinboard_api/tag"
|
6
9
|
require "pinboard_api/user"
|
@@ -10,30 +13,33 @@ module PinboardApi
|
|
10
13
|
|
11
14
|
class << self
|
12
15
|
attr_accessor :username, :password, :adapter, :ssl_options
|
16
|
+
end
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
|
18
|
+
def self.adapter
|
19
|
+
@adapter ||= :net_http
|
20
|
+
end
|
17
21
|
|
18
|
-
|
19
|
-
|
20
|
-
|
22
|
+
def self.ssl_options
|
23
|
+
@ssl_options ||= {}
|
24
|
+
end
|
21
25
|
|
22
|
-
|
23
|
-
|
24
|
-
|
26
|
+
def self.api_version
|
27
|
+
"v1"
|
28
|
+
end
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
30
|
+
def self.api_url
|
31
|
+
"https://#{username}:#{password}@api.pinboard.in"
|
32
|
+
end
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
34
|
+
def self.connection
|
35
|
+
Faraday.new(url: api_url, ssl: ssl_options) do |builder|
|
36
|
+
builder.response :logger if ENV["PINBOARD_LOGGER"]
|
37
|
+
builder.response :xml, content_type: /\bxml$/
|
38
|
+
builder.adapter adapter
|
36
39
|
end
|
37
40
|
end
|
38
41
|
|
42
|
+
def self.request(path, options = {}, &blk)
|
43
|
+
PinboardApi.connection.get(path, options, &blk)
|
44
|
+
end
|
39
45
|
end
|
data/lib/pinboard_api/post.rb
CHANGED
@@ -1,55 +1,137 @@
|
|
1
1
|
module PinboardApi
|
2
2
|
class Post
|
3
3
|
|
4
|
-
attr_reader :description, :extended, :hash, :
|
4
|
+
attr_reader :description, :extended, :hash, :meta, :url
|
5
5
|
|
6
6
|
def initialize(attributes = {})
|
7
|
+
attributes.stringify_keys!
|
8
|
+
|
7
9
|
@description = attributes["description"]
|
8
10
|
@extended = attributes["extended"]
|
9
11
|
@hash = attributes["hash"]
|
10
|
-
@href = attributes["href"]
|
11
12
|
@meta = attributes["meta"]
|
13
|
+
@url = attributes["url"] || attributes["href"]
|
12
14
|
@tags = attributes["tags"] || attributes["tag"]
|
13
15
|
@time = attributes["time"] || Time.now
|
14
16
|
end
|
15
17
|
|
16
18
|
def time
|
17
|
-
|
18
|
-
@time
|
19
|
-
elsif @time.is_a?(Date)
|
20
|
-
@time.to_time
|
21
|
-
else
|
22
|
-
Time.parse(@time)
|
23
|
-
end
|
19
|
+
@time.is_a?(String) ? Time.parse(@time) : @time.to_time
|
24
20
|
end
|
25
21
|
|
26
22
|
def tags
|
27
23
|
@tags.is_a?(String) ? @tags.split(/\s+/) : @tags
|
28
24
|
end
|
29
25
|
|
26
|
+
def destroy
|
27
|
+
path = "/#{PinboardApi.api_version}/posts/delete"
|
28
|
+
body = PinboardApi.request(path, url: @url).body["result"]
|
29
|
+
|
30
|
+
if body && body.fetch("code", "") == "done"
|
31
|
+
self
|
32
|
+
else
|
33
|
+
raise RuntimeError, "unknown response"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.destroy(url)
|
38
|
+
if post = find(url: url).first
|
39
|
+
post.destroy
|
40
|
+
else
|
41
|
+
raise RuntimeError, "unknown response"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.all(options = {})
|
46
|
+
path = "/#{PinboardApi.api_version}/posts/all"
|
47
|
+
|
48
|
+
tag = tag_param_string(options[:tag])
|
49
|
+
fromdt = dt_param_string(options[:fromdt])
|
50
|
+
todt = dt_param_string(options[:todt])
|
51
|
+
|
52
|
+
response = PinboardApi.request(path) do |req|
|
53
|
+
req.params["tag"] = tag if tag
|
54
|
+
req.params["start"] = options[:start] if options[:start]
|
55
|
+
req.params["results"] = options[:results] if options[:results]
|
56
|
+
req.params["fromdt"] = fromdt if fromdt
|
57
|
+
req.params["todt"] = todt if todt
|
58
|
+
req.params["meta"] = 1 if options[:meta]
|
59
|
+
end
|
60
|
+
|
61
|
+
extract_posts(response.body["posts"])
|
62
|
+
end
|
30
63
|
|
31
64
|
def self.find(options = {})
|
32
65
|
path = "/#{PinboardApi.api_version}/posts/get"
|
33
|
-
response = PinboardApi.
|
66
|
+
response = PinboardApi.request(path) do |req|
|
34
67
|
options.each_pair { |k,v| req.params[k.to_s] = v }
|
35
68
|
end
|
69
|
+
extract_posts(response.body["posts"])
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.last_update
|
73
|
+
path = "/#{PinboardApi.api_version}/posts/update"
|
74
|
+
body = PinboardApi.request(path).body
|
75
|
+
Time.parse(body["update"]["time"])
|
76
|
+
end
|
36
77
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
78
|
+
def self.suggest(url)
|
79
|
+
path = "/#{PinboardApi.api_version}/posts/suggest"
|
80
|
+
response = PinboardApi.request(path, url: url)
|
81
|
+
response.body["suggested"]
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.recent(options = {})
|
85
|
+
path = "/#{PinboardApi.api_version}/posts/recent"
|
86
|
+
tag = tag_param_string(options[:tag])
|
87
|
+
count = options[:count]
|
88
|
+
|
89
|
+
response = PinboardApi.request(path) do |req|
|
90
|
+
req.params["tag"] = tag if tag
|
91
|
+
req.params["count"] = count if count
|
92
|
+
end
|
93
|
+
|
94
|
+
extract_posts(response.body["posts"])
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.dates(options = {})
|
98
|
+
path = "/#{PinboardApi.api_version}/posts/dates"
|
99
|
+
tag = tag_param_string(options[:tag])
|
100
|
+
|
101
|
+
response = PinboardApi.request(path) do |req|
|
102
|
+
req.params["tag"] = tag if tag
|
103
|
+
end
|
104
|
+
|
105
|
+
dates = response.body["dates"]["date"]
|
106
|
+
dates.map do |date|
|
107
|
+
{ "count" => date["count"].to_i, "date" => Date.parse(date["date"]) }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
def self.extract_posts(payload)
|
113
|
+
unless payload.respond_to?(:keys) && payload.keys.include?("post")
|
114
|
+
return Array.new
|
115
|
+
end
|
116
|
+
|
117
|
+
# response.body["posts"] - "429 Too Many Requests. Wait 60 seconds before fetching posts/all again."
|
118
|
+
|
119
|
+
payload.inject([]) do |collection, (key, attrs)|
|
120
|
+
if key == "post"
|
121
|
+
Array.wrap(attrs).each do |post|
|
122
|
+
Array.wrap(collection) << new(post)
|
123
|
+
end
|
42
124
|
end
|
43
|
-
|
44
|
-
Array.new
|
125
|
+
collection
|
45
126
|
end
|
46
127
|
end
|
47
128
|
|
48
|
-
def self.
|
49
|
-
|
50
|
-
body = PinboardApi.connection.get(path).body
|
51
|
-
Time.parse(body["update"]["time"])
|
129
|
+
def self.dt_param_string(time)
|
130
|
+
time.nil? ? nil : time.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
52
131
|
end
|
53
132
|
|
133
|
+
def self.tag_param_string(tags)
|
134
|
+
tags.nil? ? nil : Array.wrap(tags).join(",")
|
135
|
+
end
|
54
136
|
end
|
55
137
|
end
|
data/lib/pinboard_api/tag.rb
CHANGED
@@ -9,25 +9,22 @@ module PinboardApi
|
|
9
9
|
|
10
10
|
def rename(new_name)
|
11
11
|
path = "/#{PinboardApi.api_version}/tags/rename"
|
12
|
-
response = PinboardApi.
|
13
|
-
req.params["old"] =
|
12
|
+
response = PinboardApi.request(path) do |req|
|
13
|
+
req.params["old"] = @name
|
14
14
|
req.params["new"] = new_name.to_s
|
15
15
|
end
|
16
16
|
body = response.body
|
17
17
|
|
18
18
|
if body["result"] == "done"
|
19
|
-
Tag.new("name" => new_name, "count" =>
|
19
|
+
Tag.new("name" => new_name, "count" => @count)
|
20
20
|
else
|
21
21
|
raise body["result"].to_s
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
25
|
+
def destroy
|
26
26
|
path = "/#{PinboardApi.api_version}/tags/delete"
|
27
|
-
|
28
|
-
req.params["tag"] = self.name
|
29
|
-
end
|
30
|
-
body = response.body
|
27
|
+
body = PinboardApi.request(path, tag: @name).body
|
31
28
|
|
32
29
|
if body["result"] == "done"
|
33
30
|
self
|
@@ -38,13 +35,18 @@ module PinboardApi
|
|
38
35
|
|
39
36
|
def self.all
|
40
37
|
path = "/#{PinboardApi.api_version}/tags/get"
|
41
|
-
body = PinboardApi.
|
38
|
+
body = PinboardApi.request(path).body
|
42
39
|
body["tags"]["tag"].map { |tag| new(tag) }
|
40
|
+
rescue
|
41
|
+
raise RuntimeError, "unknown response"
|
43
42
|
end
|
44
43
|
|
45
44
|
def self.find(name)
|
46
45
|
all.detect { |t| t.name == name }
|
47
46
|
end
|
48
47
|
|
48
|
+
def self.destroy(tag)
|
49
|
+
find(tag).destroy
|
50
|
+
end
|
49
51
|
end
|
50
52
|
end
|