net-flickr 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2007-2008 Ryan Grove <ryan@wonko.com>
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name of this project nor the names of its contributors may be
13
+ used to endorse or promote products derived from this software without
14
+ specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,218 @@
1
+ #--
2
+ # Copyright (c) 2007-2008 Ryan Grove <ryan@wonko.com>
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of this project nor the names of its contributors may be
14
+ # used to endorse or promote products derived from this software without
15
+ # specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ #++
28
+
29
+ # Append this file's directory to the include path if it's not there already.
30
+ $:.unshift(File.dirname(__FILE__))
31
+ $:.uniq!
32
+
33
+ # stdlib includes
34
+ require 'digest/md5'
35
+ require 'net/http'
36
+ require 'uri'
37
+
38
+ # RubyGems includes
39
+ require 'rubygems'
40
+ require 'hpricot'
41
+
42
+ # Net::Flickr includes
43
+ require 'flickr/auth'
44
+ require 'flickr/errors'
45
+ require 'flickr/list'
46
+ require 'flickr/people'
47
+ require 'flickr/person'
48
+ require 'flickr/photo'
49
+ require 'flickr/photolist'
50
+ require 'flickr/photos'
51
+ require 'flickr/tag'
52
+
53
+ module Net
54
+
55
+ # = Net::Flickr
56
+ #
57
+ # This library implements Flickr's REST API. Its usage should be pretty
58
+ # straightforward. See below for examples.
59
+ #
60
+ # Author:: Ryan Grove (mailto:ryan@wonko.com)
61
+ # Version:: 0.0.1
62
+ # Copyright:: Copyright (c) 2007-2008 Ryan Grove. All rights reserved.
63
+ # License:: New BSD License (http://opensource.org/licenses/bsd-license.php)
64
+ # Website:: http://code.google.com/p/net-flickr/
65
+ #
66
+ # == APIs not yet implemented
67
+ #
68
+ # * activity
69
+ # * blogs
70
+ # * contacts
71
+ # * favorites
72
+ # * groups
73
+ # * groups.pools
74
+ # * interestingness
75
+ # * photos.comments
76
+ # * photos.geo
77
+ # * photos.licenses
78
+ # * photos.notes
79
+ # * photos.transform
80
+ # * photos.upload
81
+ # * photosets
82
+ # * photosets.comments
83
+ # * reflection
84
+ # * tags
85
+ # * test
86
+ # * urls
87
+ #
88
+ class Flickr
89
+ AUTH_URL = 'http://flickr.com/services/auth/'.freeze
90
+ REST_ENDPOINT = 'http://api.flickr.com/services/rest/'.freeze
91
+ VERSION = '0.0.1'.freeze
92
+
93
+ attr_accessor :timeout
94
+ attr_reader :api_key, :api_secret
95
+
96
+ # Creates a new Net::Flickr object that will use the specified _api_key_ and
97
+ # _api_secret_ to connect to Flickr. If you don't already have a Flickr API
98
+ # key, you can get one at http://flickr.com/services/api/keys.
99
+ #
100
+ # If you don't provide an _api_secret_, you won't be able to make API calls
101
+ # requiring authentication.
102
+ def initialize(api_key, api_secret = nil)
103
+ @api_key = api_key
104
+ @api_secret = api_secret
105
+
106
+ # Initialize dependent classes.
107
+ @auth = Auth.new(self)
108
+ @people = People.new(self)
109
+ @photos = Photos.new(self)
110
+ end
111
+
112
+ # Returns a Net::Flickr::Auth instance.
113
+ def auth
114
+ @auth
115
+ end
116
+
117
+ # Parses the specified Flickr REST response. If the response indicates a
118
+ # successful request, the response block will be returned as an Hpricot
119
+ # element. Otherwise, an error will be raised.
120
+ def parse_response(response_xml)
121
+ begin
122
+ xml = Hpricot::XML(response_xml)
123
+ rescue => e
124
+ raise InvalidResponse, 'Invalid Flickr API response'
125
+ end
126
+
127
+ unless rsp = xml.at('/rsp')
128
+ raise InvalidResponse, 'Invalid Flickr API response'
129
+ end
130
+
131
+ if rsp['stat'] == 'ok'
132
+ return rsp
133
+ elsif rsp['stat'] == 'fail'
134
+ raise APIError, rsp.at('/err')['msg']
135
+ else
136
+ raise InvalidResponse, 'Invalid Flickr API response'
137
+ end
138
+ end
139
+
140
+ # Returns a Net::Flickr::People instance.
141
+ def people
142
+ @people
143
+ end
144
+
145
+ # Returns a Net::Flickr::Photos instance.
146
+ def photos
147
+ @photos
148
+ end
149
+
150
+ # Calls the specified Flickr REST API _method_ with the supplied arguments
151
+ # and returns a Flickr REST response in XML format. If an API secret is set,
152
+ # the request will be properly signed.
153
+ def request(method, args = {})
154
+ params = args.merge({'method' => method, 'api_key' => @api_key})
155
+ url = URI.parse(REST_ENDPOINT)
156
+ http = Net::HTTP.new(url.host, url.port)
157
+ request = sign_request(Net::HTTP::Post.new(url.path), params)
158
+
159
+ http.start do |http|
160
+ if block_given?
161
+ http.request(request) {|response| yield response }
162
+ else
163
+ response = http.request(request)
164
+
165
+ # Raise a Net::HTTP error if the HTTP request failed.
166
+ unless response.is_a?(Net::HTTPSuccess) ||
167
+ response.is_a?(Net::HTTPRedirection)
168
+ response.error!
169
+ end
170
+
171
+ # Return the parsed response.
172
+ return parse_response(response.body)
173
+ end
174
+ end
175
+ end
176
+
177
+ # Signs a Flickr API request with the API secret if set.
178
+ def sign_request(request, params)
179
+ # If the secret isn't set, we can't sign anything.
180
+ if @api_secret.nil?
181
+ request.set_form_data(params)
182
+ return request
183
+ end
184
+
185
+ # Add auth_token to the param list if we're already authenticated.
186
+ params['auth_token'] = @auth.token unless @auth.token.nil?
187
+
188
+ # Build a sorted, concatenated parameter list as described at
189
+ # http://flickr.com/services/api/auth.spec.html
190
+ paramlist = ''
191
+ params.keys.sort.each {|key| paramlist << key <<
192
+ URI.escape(params[key].to_s) }
193
+
194
+ # Sign the request with a hash of the secret key and the concatenated
195
+ # parameter list.
196
+ params['api_sig'] = Digest::MD5.hexdigest(@api_secret + paramlist)
197
+ request.set_form_data(params)
198
+
199
+ return request
200
+ end
201
+
202
+ # Signs a Flickr URL with the API secret if set.
203
+ def sign_url(url)
204
+ return url if @api_secret.nil?
205
+
206
+ uri = URI.parse(url)
207
+
208
+ params = uri.query.split('&')
209
+ params << 'api_sig=' + Digest::MD5.hexdigest(@api_secret +
210
+ params.sort.join('').gsub('=', ''))
211
+
212
+ uri.query = params.join('&')
213
+
214
+ return uri.to_s
215
+ end
216
+ end
217
+
218
+ end
@@ -0,0 +1,139 @@
1
+ #--
2
+ # Copyright (c) 2007-2008 Ryan Grove <ryan@wonko.com>
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of this project nor the names of its contributors may be
14
+ # used to endorse or promote products derived from this software without
15
+ # specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ #++
28
+
29
+ module Net; class Flickr
30
+
31
+ # Implements the Flickr authentication API. Please see
32
+ # http://flickr.com/services/api/auth.spec.html for details on how to use this
33
+ # API in your application.
34
+ #
35
+ # Don't instantiate this class yourself. Instead, create an instance of the
36
+ # +Flickr+ class and then user <tt>Flickr.auth</tt> to access this class,
37
+ # like so:
38
+ #
39
+ # require 'net/flickr'
40
+ #
41
+ # flickr = Net::Flickr.new('524266cbd9d3c2xa2679fee8b337fip2',
42
+ # '835hae5d6j0sd47a')
43
+ #
44
+ # puts flickr.auth.url_desktop
45
+ #
46
+ class Auth
47
+ PERM_NONE = :none
48
+ PERM_READ = :read
49
+ PERM_WRITE = :write
50
+ PERM_DELETE = :delete
51
+
52
+ attr_reader :frob, :perms, :token, :user_id, :user_name, :user_fullname
53
+
54
+ def initialize(flickr)
55
+ @flickr = flickr
56
+
57
+ @frob = nil
58
+ @perms = PERM_NONE
59
+ @token = nil
60
+ @user_id = nil
61
+ @user_name = nil
62
+ @user_fullname = nil
63
+ end
64
+
65
+ #--
66
+ # Public Instance Methods
67
+ #++
68
+
69
+ # Updates this Auth object with the credentials attached to the specified
70
+ # authentication _token_. If the _token_ is not valid, an APIError will be
71
+ # raised.
72
+ def check_token(token = @token)
73
+ update_auth(@flickr.request('flickr.auth.checkToken',
74
+ 'auth_token' => token))
75
+ return true
76
+ end
77
+
78
+ # Gets the full authentication token for the specified _mini_token_.
79
+ def full_token(mini_token)
80
+ update_auth(@flickr.request('flickr.auth.getFullToken',
81
+ 'mini_token' => mini_token))
82
+ return @token
83
+ end
84
+
85
+ # Gets a frob to be used during authentication.
86
+ def get_frob
87
+ response = @flickr.request('flickr.auth.getFrob').at('frob')
88
+ return @frob = response.inner_text
89
+ end
90
+
91
+ # Updates this Auth object with the credentials for the specified _frob_ and
92
+ # returns an auth token. If the _frob_ is not valid, an APIError will be
93
+ # raised.
94
+ def get_token(frob = @frob)
95
+ update_auth(@flickr.request('flickr.auth.getToken', 'frob' => frob))
96
+ return @token
97
+ end
98
+
99
+ # Gets a signed URL that can by used by a desktop application to show the
100
+ # user a Flickr authentication screen. Once the user has visited this URL
101
+ # and authorized your application, you can call get_token to authenticate.
102
+ def url_desktop(perms)
103
+ get_frob if @frob.nil?
104
+ url = Flickr::AUTH_URL +
105
+ "?api_key=#{@flickr.api_key}&perms=#{perms}&frob=#{@frob}"
106
+
107
+ return @flickr.sign_url(url)
108
+ end
109
+
110
+ # Gets a signed URL that can be used by a web application to show the user a
111
+ # Flickr authentication screen. Once the user has visited this URL and
112
+ # authorized your application, you can call get_token with the frob provided
113
+ # by Flickr to authenticate.
114
+ def url_webapp(perms)
115
+ return @flickr.sign_url(Flickr::AUTH_URL +
116
+ "?api_key=#{@flickr.api_key}&perms=#{perms}")
117
+ end
118
+
119
+ #--
120
+ # Private Instance Methods
121
+ #++
122
+
123
+ private
124
+
125
+ # Updates this Auth object with the credentials in the specified XML
126
+ # _response_.
127
+ def update_auth(response)
128
+ auth = response.at('auth')
129
+ user = auth.at('user')
130
+
131
+ @perms = auth.at('perms').inner_text.to_sym
132
+ @token = auth.at('token').inner_text
133
+ @user_id = user['nsid']
134
+ @user_name = user['username']
135
+ @user_fullname = user['fullname']
136
+ end
137
+ end
138
+
139
+ end; end
@@ -0,0 +1,35 @@
1
+ #--
2
+ # Copyright (c) 2007-2008 Ryan Grove <ryan@wonko.com>
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of this project nor the names of its contributors may be
14
+ # used to endorse or promote products derived from this software without
15
+ # specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ #++
28
+
29
+ module Net; class Flickr
30
+
31
+ class APIError < StandardError; end
32
+ class InvalidResponse < StandardError; end
33
+ class ListError < StandardError; end
34
+
35
+ end; end
@@ -0,0 +1,135 @@
1
+ #--
2
+ # Copyright (c) 2007-2008 Ryan Grove <ryan@wonko.com>
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of this project nor the names of its contributors may be
14
+ # used to endorse or promote products derived from this software without
15
+ # specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ #++
28
+
29
+ module Net; class Flickr
30
+
31
+ # Base class for paginated lists.
32
+ #
33
+ # Don't instantiate this class yourself. It's a base class extended by
34
+ # +PhotoList+ and others.
35
+ class List
36
+ include Enumerable
37
+
38
+ attr_reader :page, :pages, :per_page, :total
39
+
40
+ def initialize(flickr, load_method, load_args)
41
+ @flickr = flickr
42
+ @load_method = load_method
43
+ @load_args = load_args
44
+
45
+ update_list
46
+ end
47
+
48
+ #--
49
+ # Public Instance Methods
50
+ #++
51
+
52
+ def [](index)
53
+ return @items[index]
54
+ end
55
+
56
+ def each
57
+ @items.each {|item| yield item }
58
+ end
59
+
60
+ # Returns +true+ if the current page is the first page of the list, +false+
61
+ # otherwise.
62
+ def first_page?
63
+ return @page == 1
64
+ end
65
+
66
+ # Returns +true+ if the current page is the last page of the list, +false+
67
+ # otherwise.
68
+ def last_page?
69
+ return @page == @pages
70
+ end
71
+
72
+ # Loads the next page in the list.
73
+ def next
74
+ if last_page?
75
+ raise ListError, 'Already on the last page of the list'
76
+ end
77
+
78
+ @load_args['page'] = @page + 1
79
+ update_list
80
+ end
81
+
82
+ # Loads the specified page in the list.
83
+ def page=(page)
84
+ if page < 1 || page > @pages
85
+ raise ArgumentError, 'Page number out of bounds'
86
+ end
87
+
88
+ @load_args['page'] = page
89
+ update_list
90
+ end
91
+
92
+ # Sets the number of items loaded per page. Must be between 1 and 500
93
+ # inclusive.
94
+ def per_page=(per_page)
95
+ if per_page < 1 || per_page > 500
96
+ raise ArgumentError, 'per_page must be between 1 and 500 inclusive'
97
+ end
98
+
99
+ @per_page = per_page
100
+ @load_args['per_page'] = @per_page
101
+ end
102
+
103
+ # Loads the previous page in the list.
104
+ def previous
105
+ if first_page?
106
+ raise ListError, 'Already on the first page of the list'
107
+ end
108
+
109
+ @load_args['page'] = @page - 1
110
+ update_list
111
+ end
112
+
113
+ alias prev previous
114
+
115
+ #--
116
+ # Private Instance Methods
117
+ #++
118
+
119
+ private
120
+
121
+ def update_list
122
+ @items = []
123
+
124
+ @response = @flickr.request(@load_method, @load_args).
125
+ at('/*[@page]:first')
126
+
127
+ @per_page = @response['perpage'].to_i
128
+ @page = @response['page'].to_i
129
+ @pages = @response['pages'].to_i
130
+ @total = @response['total'].to_i
131
+ end
132
+
133
+ end
134
+
135
+ end; end