net-flickr 0.0.1

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