dgiunta-flickr 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/History.txt +54 -0
  2. data/LICENSE +20 -0
  3. data/README.txt +74 -0
  4. data/TODO +6 -0
  5. data/lib/flickr.rb +732 -0
  6. data/test/test_flickr.rb +1104 -0
  7. metadata +66 -0
data/History.txt ADDED
@@ -0,0 +1,54 @@
1
+ == 1.0.7 2008-11-26
2
+ * 1 major enhancement
3
+ * When a collection of photos is fetched (e.g. by Flickr#search), a PhotoCollection object is now returned, a type of array, which allows easy access to the pagination information returned by Flickr (e.g. total items, pages, etc). Previously this info was lost
4
+ * Minor enhancements:
5
+ * Refactored parsing of response when getting info about photo (public API is not changed).
6
+ * All returned info is now parsed and stored (previously only some attributes were)
7
+ * A record is kept of whether info has been previously fetched from Flickr (avoids unnecessary extra calls)
8
+ * Improved User#getInfo. No longer makes API call for pretty_url (which was in addition to main getInfo call) or photos_url
9
+ * Bugfixes
10
+ * Fixed Photo#source not to make unnecessary API call
11
+ * Fixed User#url not to make unnecessary API call
12
+
13
+ == 1.0.6 2008-07-30
14
+ * Bugfixes:
15
+ * fixed Flickr#photos when used for searching
16
+ * fixed Flickr::Photo#url to not use usernames (if the user changes their username, the url will wrong)
17
+ * fixed Flickr#related_tags
18
+ * Flickr::Photo#to_s was making unnecessary API calls
19
+ * fixed 'test' Rake task
20
+ * fixed 'gem' Rake task
21
+ * Minor enhancements:
22
+ * added Flickr#search as an alias for Flickr#photos in its search mode
23
+ * added Flickr#recent as an alias for Flickr#photos for recent photos
24
+ * added Flickr::Photo#pretty_url for the URL that includes a username if present
25
+ * added Flickr::Photo#size_url which is like url except 'Medium' works the same as other sizes
26
+ * allow lowercase ('medium') and symbol (:medium) forms for sizes
27
+ * internal code cleanup
28
+
29
+ == 1.0.5 2008-05-12
30
+
31
+ * 1 major change:
32
+ * Updated and refactored Flickr::Group class and Flickr#groups method to work with current Flickr API. Flickr#groups now searches for given group, rather than groups.getActiveList (which no longer exists as Flickr API call)
33
+ * Minor enhancements:
34
+ * Tweaked internals so new client instance isn't created each time new object (e.g. photo, user) is created
35
+ * Improved test coverage
36
+
37
+ == 1.0.4 2008-05-11
38
+
39
+ * 1 major enhancement:
40
+ * Added authentication facility as per current Flickr API
41
+ * 3 minor enhancements:
42
+ * Improved test suite
43
+ * Improved API for creating Flickr objects (old one still works)
44
+ * Protected methods that are only used internally (e.g. request, request_url)
45
+
46
+ == 1.0.3 2008-04-18
47
+
48
+ * Various bugfixes:
49
+ * User instantiation was broken (wrong number of params passed)
50
+ * Instantiating photos failed in several places if single photo returned
51
+ * Photosets#getInfo would always fail as the parameter name should be photoset_id, not photosets_id
52
+ * Removed call to flickr.people.getOnlineList in Flickr#users as that call is no longer in the Flickr API
53
+ * Performance improvements:
54
+ * Url and image source uri now generated without a call to the Flickr API, as per the Flickr API docs
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Scott Raymond, Patrick Plattes, Chris Taggart
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.txt ADDED
@@ -0,0 +1,74 @@
1
+ = flickr
2
+
3
+ http://github.com/ctagg/flickr
4
+
5
+ == DESCRIPTION:
6
+
7
+ An insanely easy interface to the Flickr photo-sharing service. By Scott Raymond. (& updated May 08 by Chris Taggart, http://pushrod.wordpress.com)
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ The flickr gem (famously featured in a RubyonRails screencast) had broken with Flickr's new authentication scheme and updated API.
12
+ This has now been largely corrected, though not all current API calls are supported yet.
13
+
14
+ == SYNOPSIS:
15
+
16
+ require 'flickr'
17
+ flickr = Flickr.new('some_flickr_api_key') # create a flickr client (get an API key from http://www.flickr.com/services/api/)
18
+ user = flickr.users('sco@scottraymond.net') # lookup a user
19
+ user.name # get the user's name
20
+ user.location # and location
21
+ user.photos # grab their collection of Photo objects...
22
+ user.groups # ...the groups they're in...
23
+ user.contacts # ...their contacts...
24
+ user.favorites # ...favorite photos...
25
+ user.photosets # ...their photo sets...
26
+ user.tags # ...their tags...
27
+ user.popular_tags # ...and their popular tags
28
+ recentphotos = flickr.photos # get the 100 most recent public photos
29
+ photo = recentphotos.first # or very most recent one
30
+ photo.url # see its URL,
31
+ photo.title # title,
32
+ photo.description # and description,
33
+ photo.owner # and its owner.
34
+ File.open(photo.filename, 'w') do |file|
35
+ file.puts p.file # save the photo to a local file
36
+ end
37
+ flickr.photos.each do |p| # get the last 100 public photos...
38
+ File.open(p.filename, 'w') do |f|
39
+ f.puts p.file('Square') # ...and save a local copy of their square thumbnail
40
+ end
41
+ end
42
+
43
+ == REQUIREMENTS:
44
+
45
+ * Xmlsimple gem
46
+
47
+ == INSTALL:
48
+
49
+ * sudo gem install flickr
50
+
51
+ == LICENSE:
52
+
53
+ (The MIT License)
54
+
55
+ Copyright (c) 2008 Scott Raymond, Patrick Plattes, Chris Taggart
56
+
57
+ Permission is hereby granted, free of charge, to any person obtaining
58
+ a copy of this software and associated documentation files (the
59
+ 'Software'), to deal in the Software without restriction, including
60
+ without limitation the rights to use, copy, modify, merge, publish,
61
+ distribute, sublicense, and/or sell copies of the Software, and to
62
+ permit persons to whom the Software is furnished to do so, subject to
63
+ the following conditions:
64
+
65
+ The above copyright notice and this permission notice shall be
66
+ included in all copies or substantial portions of the Software.
67
+
68
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
69
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
70
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
71
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
72
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
73
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
74
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/TODO ADDED
@@ -0,0 +1,6 @@
1
+ TODO:
2
+ Complete test suite for untested methods
3
+ Bring outdated methods up-to-date
4
+ Write documentation
5
+ Add missing Flickr API call
6
+ Convert dates to ruby dates
data/lib/flickr.rb ADDED
@@ -0,0 +1,732 @@
1
+ # = Flickr
2
+ # An insanely easy interface to the Flickr photo-sharing service. By Scott Raymond.
3
+ #
4
+ # Author:: Scott Raymond <sco@redgreenblu.com>
5
+ # Copyright:: Copyright (c) 2005 Scott Raymond <sco@redgreenblu.com>. Additional content by Patrick Plattes and Chris Taggart (http://pushrod.wordpress.com)
6
+ # License:: MIT <http://www.opensource.org/licenses/mit-license.php>
7
+ #
8
+ # BASIC USAGE:
9
+ # require 'flickr'
10
+ # flickr = Flickr.new('some_flickr_api_key') # create a flickr client (get an API key from http://www.flickr.com/services/api/)
11
+ # user = flickr.users('sco@scottraymond.net') # lookup a user
12
+ # user.name # get the user's name
13
+ # user.location # and location
14
+ # user.photos # grab their collection of Photo objects...
15
+ # user.groups # ...the groups they're in...
16
+ # user.contacts # ...their contacts...
17
+ # user.favorites # ...favorite photos...
18
+ # user.photosets # ...their photo sets...
19
+ # user.tags # ...and their tags
20
+ # recentphotos = flickr.photos # get the 100 most recent public photos
21
+ # photo = recentphotos.first # or very most recent one
22
+ # photo.url # see its URL,
23
+ # photo.title # title,
24
+ # photo.description # and description,
25
+ # photo.owner # and its owner.
26
+ # File.open(photo.filename, 'w') do |file|
27
+ # file.puts p.file # save the photo to a local file
28
+ # end
29
+ # flickr.photos.each do |p| # get the last 100 public photos...
30
+ # File.open(p.filename, 'w') do |f|
31
+ # f.puts p.file('Square') # ...and save a local copy of their square thumbnail
32
+ # end
33
+ # end
34
+
35
+
36
+ require 'cgi'
37
+ require 'net/http'
38
+ require 'xmlsimple'
39
+ require 'digest/md5'
40
+
41
+ # Flickr client class. Requires an API key
42
+ class Flickr
43
+ attr_reader :api_key, :auth_token
44
+ attr_accessor :user
45
+
46
+ HOST_URL = 'http://flickr.com'
47
+ API_PATH = '/services/rest'
48
+
49
+ # Flickr, annoyingly, uses a number of representations to specify the size
50
+ # of a photo, depending on the context. It gives a label such a "Small" or
51
+ # "Medium" to a size of photo, when returning all possible sizes. However,
52
+ # when generating the uri for the page that features that size of photo, or
53
+ # the source url for the image itself it uses a single letter. Bizarrely,
54
+ # these letters are different depending on whether you want the Flickr page
55
+ # for the photo or the source uri -- e.g. a "Small" photo (240 pixels on its
56
+ # longest side) may be viewed at
57
+ # "http://www.flickr.com/photos/sco/2397458775/sizes/s/"
58
+ # but its source is at
59
+ # "http://farm4.static.flickr.com/3118/2397458775_2ec2ddc324_m.jpg".
60
+ # The VALID_SIZES hash associates the correct letter with a label
61
+ VALID_SIZES = { "Square" => ["s", "sq"],
62
+ "Thumbnail" => ["t", "t"],
63
+ "Small" => ["m", "s"],
64
+ "Medium" => [nil, "m"],
65
+ "Large" => ["b", "l"]
66
+ }
67
+
68
+ # To use the Flickr API you need an api key
69
+ # (see http://www.flickr.com/services/api/misc.api_keys.html), and the flickr
70
+ # client object shuld be initialized with this. You'll also need a shared
71
+ # secret code if you want to use authentication (e.g. to get a user's
72
+ # private photos)
73
+ # There are two ways to initialize the Flickr client. The preferred way is with
74
+ # a hash of params, e.g. 'api_key' => 'your_api_key', 'shared_secret' =>
75
+ # 'shared_secret_code'. The older (deprecated) way is to pass an ordered series of
76
+ # arguments. This is provided for continuity only, as several of the arguments
77
+ # are no longer usable ('email', 'password')
78
+ def initialize(api_key_or_params=nil, email=nil, password=nil, shared_secret=nil)
79
+ @host = HOST_URL
80
+ @api = API_PATH
81
+ if api_key_or_params.is_a?(Hash)
82
+ @api_key = api_key_or_params['api_key']
83
+ @shared_secret = api_key_or_params['shared_secret']
84
+ @auth_token = api_key_or_params['auth_token']
85
+ else
86
+ @api_key = api_key_or_params
87
+ @shared_secret = shared_secret
88
+ login(email, password) if email and password
89
+ end
90
+ end
91
+
92
+ # Gets authentication token given a Flickr frob, which is returned when user
93
+ # allows access to their account for the application with the api_key which
94
+ # made the request
95
+ def get_token_from(frob)
96
+ auth_response = request("auth.getToken", :frob => frob)['auth']
97
+ @auth_token = auth_response['token']
98
+ @user = User.new( 'id' => auth_response['user']['nsid'],
99
+ 'username' => auth_response['user']['username'],
100
+ 'name' => auth_response['user']['fullname'],
101
+ 'client' => self)
102
+ @auth_token
103
+ end
104
+
105
+ # Stores authentication credentials to use on all subsequent calls.
106
+ # If authentication succeeds, returns a User object.
107
+ # NB This call is no longer in API and will result in an error if called
108
+ def login(email='', password='')
109
+ @email = email
110
+ @password = password
111
+ user = request('test.login')['user'] rescue fail
112
+ @user = User.new(user['id'], nil, nil, nil, @api_key)
113
+ end
114
+
115
+ # Implements flickr.urls.lookupGroup and flickr.urls.lookupUser
116
+ def find_by_url(url)
117
+ response = urls_lookupUser('url'=>url) rescue urls_lookupGroup('url'=>url) rescue nil
118
+ (response['user']) ? User.new(response['user']['id'], nil, nil, nil, @api_key) : Group.new(response['group']['id'], @api_key) unless response.nil?
119
+ end
120
+
121
+ # Implements flickr.photos.getRecent and flickr.photos.search
122
+ def photos(*criteria)
123
+ criteria ? photos_search(*criteria) : recent
124
+ end
125
+
126
+ # flickr.photos.getRecent
127
+ # 100 newest photos from everyone
128
+ def recent
129
+ photos_request('photos.getRecent')
130
+ end
131
+
132
+ def photos_search(params={})
133
+ photos_request('photos.search', params)
134
+ end
135
+ alias_method :search, :photos_search
136
+
137
+ # Gets public photos with a given tag
138
+ def tag(tag)
139
+ photos('tags'=>tag)
140
+ end
141
+
142
+ # Implements flickr.people.findByEmail and flickr.people.findByUsername.
143
+ #
144
+ # Updated by Dave Giunta, Giunta Creative on 01/09/2009
145
+ #
146
+ # <b>Description:</b> The original flickr library provided only flickr.people.findByEmail,
147
+ # which rescued from UserNotFound errors by checking flickr.people.findByUsername.
148
+ #
149
+ # This works great, except that Flickr also has
150
+ #
151
+ # <b>Arguments:</b>
152
+ #
153
+ # <b>Returns:</b>
154
+ #
155
+ # <em>Syntax: </em>
156
+ def users(lookup=nil)
157
+ user = find_by_url("flickr.com/people/#{lookup}") || people_findByEmail('find_email'=>lookup)['user'] rescue people_findByUsername('username'=>lookup)['user']
158
+
159
+ return case user.class.to_s
160
+ when "Flickr::User"
161
+ user
162
+ when "Hash"
163
+ User.new("id" => user["nsid"], "username" => user["username"], "client" => self)
164
+ else
165
+ raise UserNotFound
166
+ end
167
+ end
168
+
169
+ # Implements flickr.groups.search
170
+ def groups(group_name, options={})
171
+ collection = groups_search({"text" => group_name}.merge(options))['groups']['group']
172
+ collection = [collection] if collection.is_a? Hash
173
+
174
+ collection.collect { |group| Group.new( "id" => group['nsid'],
175
+ "name" => group['name'],
176
+ "eighteenplus" => group['eighteenplus'],
177
+ "client" => self) }
178
+ end
179
+
180
+ def photoset(photoset_id)
181
+ Photoset.new(photoset_id, @api_key)
182
+ end
183
+
184
+ # Implements flickr.tags.getRelated
185
+ def related_tags(tag)
186
+ tags_getRelated('tag'=>tag)['tags']['tag']
187
+ end
188
+
189
+ # Implements flickr.photos.licenses.getInfo
190
+ def licenses
191
+ photos_licenses_getInfo['licenses']['license']
192
+ end
193
+
194
+ # Returns url for user to login in to Flickr to authenticate app for a user
195
+ def login_url(perms)
196
+ "http://flickr.com/services/auth/?api_key=#{@api_key}&perms=#{perms}&api_sig=#{signature_from('api_key'=>@api_key, 'perms' => perms)}"
197
+ end
198
+
199
+ # Implements everything else.
200
+ # Any method not defined explicitly will be passed on to the Flickr API,
201
+ # and return an XmlSimple document. For example, Flickr#test_echo is not
202
+ # defined, so it will pass the call to the flickr.test.echo method.
203
+ def method_missing(method_id, params={})
204
+ request(method_id.id2name.gsub(/_/, '.'), params)
205
+ end
206
+
207
+ # Does an HTTP GET on a given URL and returns the response body
208
+ def http_get(url)
209
+ Net::HTTP.get_response(URI.parse(url)).body.to_s
210
+ end
211
+
212
+ # Takes a Flickr API method name and set of parameters; returns an XmlSimple object with the response
213
+ def request(method, params={})
214
+ url = request_url(method, params)
215
+ response = XmlSimple.xml_in(http_get(url), { 'ForceArray' => false })
216
+ raise response['err']['msg'] if response['stat'] != 'ok'
217
+ response
218
+ end
219
+
220
+ # acts like request but returns a PhotoCollection (a list of Photo objects)
221
+ def photos_request(method, params={})
222
+ photos = request(method, params)
223
+ PhotoCollection.new(photos, @api_key)
224
+ end
225
+
226
+ # Builds url for Flickr API REST request from given the flickr method name
227
+ # (exclusing the 'flickr.' that begins each method call) and params (where
228
+ # applicable) which should be supplied as a Hash (e.g 'user_id' => "foo123")
229
+ def request_url(method, params={})
230
+ method = 'flickr.' + method
231
+ url = "#{@host}#{@api}/?api_key=#{@api_key}&method=#{method}"
232
+ params.merge!('api_key' => @api_key, 'method' => method, 'auth_token' => @auth_token)
233
+ signature = signature_from(params)
234
+
235
+ url = "#{@host}#{@api}/?" + params.merge('api_sig' => signature).collect { |k,v| "#{k}=" + CGI::escape(v.to_s) unless v.nil? }.compact.join("&")
236
+ end
237
+
238
+ def signature_from(params={})
239
+ return unless @shared_secret # don't both getting signature if no shared_secret
240
+ request_str = params.reject {|k,v| v.nil?}.collect {|p| "#{p[0].to_s}#{p[1]}"}.sort.join # build key value pairs, sort in alpha order then join them, ignoring those with nil value
241
+ return Digest::MD5.hexdigest("#{@shared_secret}#{request_str}")
242
+ end
243
+
244
+ # A collection of photos is returned as a PhotoCollection, a subclass of Array.
245
+ # This allows us to retain the pagination info returned by Flickr and make it
246
+ # accessible in a friendly way
247
+ class PhotoCollection < Array
248
+ attr_reader :page, :pages, :perpage, :total
249
+
250
+ # builds a PhotoCollection from given params, such as those returned from
251
+ # photos.search API call
252
+ def initialize(photos_api_response={}, api_key=nil)
253
+ [ "page", "pages", "perpage", "total" ].each { |i| instance_variable_set("@#{i}", photos_api_response["photos"][i])}
254
+ collection = photos_api_response['photos']['photo'] || []
255
+ collection = [collection] if collection.is_a? Hash
256
+ collection.each { |photo| self << Photo.new(photo.delete('id'), api_key, photo) }
257
+ end
258
+ end
259
+
260
+ # Todo:
261
+ # logged_in?
262
+ # if logged in:
263
+ # flickr.blogs.getList
264
+ # flickr.favorites.add
265
+ # flickr.favorites.remove
266
+ # flickr.groups.browse
267
+ # flickr.photos.getCounts
268
+ # flickr.photos.getNotInSet
269
+ # flickr.photos.getUntagged
270
+ # flickr.photosets.create
271
+ # flickr.photosets.orderSets
272
+ # flickr.test.login
273
+ # uploading
274
+ class User
275
+
276
+ attr_reader :client, :id, :name, :location, :photos_url, :url, :count, :firstdate, :firstdatetaken
277
+
278
+ # A Flickr::User can be instantiated in two ways. The old (deprecated)
279
+ # method is with an ordered series of values. The new method is with a
280
+ # params Hash, which is easier when a variable number of params are
281
+ # supplied, which is the case here, and also avoids having to constantly
282
+ # supply nil values for the email and password, which are now irrelevant
283
+ # as authentication is no longer done this way.
284
+ # An associated flickr client will also be generated if an api key is
285
+ # passed among the arguments or in the params hash. Alternatively, and
286
+ # most likely, an existing client object may be passed in the params hash
287
+ # (e.g. 'client' => some_existing_flickr_client_object), and this is
288
+ # what happends when users are initlialized as the result of a method
289
+ # called on the flickr client (e.g. flickr.users)
290
+ def initialize(id_or_params_hash=nil, username=nil, email=nil, password=nil, api_key=nil)
291
+ if id_or_params_hash.is_a?(Hash)
292
+ id_or_params_hash.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
293
+ else
294
+ @id = id_or_params_hash
295
+ @username = username
296
+ @email = email
297
+ @password = password
298
+ @api_key = api_key
299
+ end
300
+ @client ||= Flickr.new('api_key' => @api_key, 'shared_secret' => @shared_secret, 'auth_token' => @auth_token) if @api_key
301
+ @client.login(@email, @password) if @email and @password # this is now irrelevant as Flickr API no longer supports authentication this way
302
+ end
303
+
304
+ def username
305
+ @username.nil? ? getInfo.username : @username
306
+ end
307
+ def name
308
+ @name.nil? ? getInfo.name : @name
309
+ end
310
+ def location
311
+ @location.nil? ? getInfo.location : @location
312
+ end
313
+ def count
314
+ @count.nil? ? getInfo.count : @count
315
+ end
316
+ def firstdate
317
+ @firstdate.nil? ? getInfo.firstdate : @firstdate
318
+ end
319
+ def firstdatetaken
320
+ @firstdatetaken.nil? ? getInfo.firstdatetaken : @firstdatetaken
321
+ end
322
+
323
+ # Builds url for user's photos page as per
324
+ # http://www.flickr.com/services/api/misc.urls.html
325
+ def photos_url
326
+ "http://www.flickr.com/photos/#{id}/"
327
+ end
328
+
329
+ # Builds url for user's profile page as per
330
+ # http://www.flickr.com/services/api/misc.urls.html
331
+ def url
332
+ "http://www.flickr.com/people/#{id}/"
333
+ end
334
+
335
+ def pretty_url
336
+ @pretty_url ||= @client.urls_getUserProfile('user_id'=>@id)['user']['url']
337
+ end
338
+
339
+ # Implements flickr.people.getPublicGroups
340
+ def groups
341
+ collection = @client.people_getPublicGroups('user_id'=>@id)['groups']['group']
342
+ collection = [collection] if collection.is_a? Hash
343
+ collection.collect { |group| Group.new( "id" => group['nsid'],
344
+ "name" => group['name'],
345
+ "eighteenplus" => group['eighteenplus'],
346
+ "client" => @client) }
347
+ end
348
+
349
+ # Implements flickr.people.getPublicPhotos. Options hash allows you to add
350
+ # extra restrictions as per flickr.people.getPublicPhotos docs, e.g.
351
+ # user.photos('per_page' => '25', 'extras' => 'date_taken')
352
+ def photos(options={})
353
+ @client.photos_request('people.getPublicPhotos', {'user_id' => @id}.merge(options))
354
+ # what about non-public photos?
355
+ end
356
+
357
+ # Gets photos with a given tag
358
+ def tag(tag)
359
+ @client.photos('user_id'=>@id, 'tags'=>tag)
360
+ end
361
+
362
+ # Implements flickr.contacts.getPublicList and flickr.contacts.getList
363
+ def contacts
364
+ @client.contacts_getPublicList('user_id'=>@id)['contacts']['contact'].collect { |contact| User.new(contact['nsid'], contact['username'], nil, nil, @api_key) }
365
+ #or
366
+ end
367
+
368
+ # Implements flickr.favorites.getPublicList
369
+ def favorites
370
+ @client.photos_request('favorites.getPublicList', 'user_id' => @id)
371
+ end
372
+
373
+ # Implements flickr.photosets.getList
374
+ def photosets
375
+ @client.photosets_getList('user_id'=>@id)['photosets']['photoset'].collect { |photoset| Photoset.new(photoset['id'], @api_key) }
376
+ end
377
+
378
+ # Implements flickr.tags.getListUser
379
+ def tags
380
+ @client.tags_getListUser('user_id'=>@id)['who']['tags']['tag'].collect { |tag| tag }
381
+ end
382
+
383
+ # Implements flickr.tags.getListUserPopular
384
+ def popular_tags(count = 10)
385
+ @client.tags_getListUserPopular('user_id'=>@id, 'count'=> count)['who']['tags']['tag'].each { |tag_score| tag_score["tag"] = tag_score.delete("content") }
386
+ end
387
+
388
+ # Implements flickr.photos.getContactsPublicPhotos and flickr.photos.getContactsPhotos
389
+ def contactsPhotos
390
+ @client.photos_request('photos.getContactsPublicPhotos', 'user_id' => @id)
391
+ end
392
+
393
+ def to_s
394
+ @name
395
+ end
396
+
397
+ private
398
+
399
+ # Implements flickr.people.getInfo, flickr.urls.getUserPhotos, and flickr.urls.getUserProfile
400
+ def getInfo
401
+ info = @client.people_getInfo('user_id'=>@id)['person']
402
+ @username = info['username']
403
+ @name = info['realname']
404
+ @location = info['location']
405
+ @count = info['photos']['count']
406
+ @firstdate = info['photos']['firstdate']
407
+ @firstdatetaken = info['photos']['firstdatetaken']
408
+ self
409
+ end
410
+
411
+ end
412
+
413
+ class Photo
414
+
415
+ attr_reader :id, :client, :title
416
+
417
+ def initialize(id=nil, api_key=nil, extra_params={})
418
+ @id = id
419
+ @api_key = api_key
420
+ extra_params.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
421
+ @client = Flickr.new @api_key
422
+ end
423
+
424
+ # Allows access to all photos instance variables through hash like
425
+ # interface, e.g. photo["datetaken"] returns @datetaken instance
426
+ # variable. Useful for accessing any weird and wonderful parameter
427
+ # that may have been returned by Flickr when finding the photo,
428
+ # e.g. those returned by the extras argument in
429
+ # flickr.people.getPublicPhotos
430
+ def [](param_name)
431
+ instance_variable_get("@#{param_name}")
432
+ end
433
+
434
+ def title
435
+ @title.nil? ? getInfo("title") : @title
436
+ end
437
+
438
+ # Returns the owner of the photo as a Flickr::User. If we have no info
439
+ # about the owner, we make an API call to get it. If we already have
440
+ # the owner's id, create a user based on that. Either way, we cache the
441
+ # result so we don't need to check again
442
+ def owner
443
+ case @owner
444
+ when Flickr::User
445
+ @owner
446
+ when String
447
+ @owner = Flickr::User.new(@owner, nil, nil, nil, @api_key)
448
+ else
449
+ getInfo("owner")
450
+ end
451
+ end
452
+
453
+ def server
454
+ @server.nil? ? getInfo("server") : @server
455
+ end
456
+
457
+ def isfavorite
458
+ @isfavorite.nil? ? getInfo("isfavorite") : @isfavorite
459
+ end
460
+
461
+ def license
462
+ @license.nil? ? getInfo("license") : @license
463
+ end
464
+
465
+ def rotation
466
+ @rotation.nil? ? getInfo("rotation") : @rotation
467
+ end
468
+
469
+ def description
470
+ @description || getInfo("description")
471
+ end
472
+
473
+ def notes
474
+ @notes.nil? ? getInfo("notes") : @notes
475
+ end
476
+
477
+ # Returns the URL for the photo size page
478
+ # defaults to 'Medium'
479
+ # other valid sizes are in the VALID_SIZES hash
480
+ def size_url(size='Medium')
481
+ uri_for_photo_from_self(size) || sizes(size)['url']
482
+ end
483
+
484
+ # converts string or symbol size to a capitalized string
485
+ def normalize_size(size)
486
+ size ? size.to_s.capitalize : size
487
+ end
488
+
489
+ # the URL for the main photo page
490
+ # if getInfo has already been called, this will return the pretty url
491
+ #
492
+ # for historical reasons, an optional size can be given
493
+ # 'Medium' returns the regular url; any other size returns a size page
494
+ # use size_url instead
495
+ def url(size = nil)
496
+ if normalize_size(size) != 'Medium'
497
+ size_url(size)
498
+ else
499
+ @url || uri_for_photo_from_self
500
+ end
501
+ end
502
+
503
+ # the 'pretty' url for a photo
504
+ # (if the user has set up a custom name)
505
+ # eg, http://flickr.com/photos/granth/2584402507/ instead of
506
+ # http://flickr.com/photos/23386158@N00/2584402507/
507
+ def pretty_url
508
+ @url || getInfo("pretty_url")
509
+ end
510
+
511
+ # Returns the URL for the image (default or any specified size)
512
+ def source(size='Medium')
513
+ image_source_uri_from_self(size) || sizes(size)['source']
514
+ end
515
+
516
+ # Returns the photo file data itself, in any specified size. Example: File.open(photo.title, 'w') { |f| f.puts photo.file }
517
+ def file(size='Medium')
518
+ Net::HTTP.get_response(URI.parse(source(size))).body
519
+ end
520
+
521
+ # Unique filename for the image, based on the Flickr NSID
522
+ def filename
523
+ "#{@id}.jpg"
524
+ end
525
+
526
+ # Implements flickr.photos.getContext
527
+ def context
528
+ context = @client.photos_getContext('photo_id'=>@id)
529
+ @previousPhoto = Photo.new(context['prevphoto'].delete('id'), @api_key, context['prevphoto']) if context['prevphoto']['id']!='0'
530
+ @nextPhoto = Photo.new(context['nextphoto'].delete('id'), @api_key, context['nextphoto']) if context['nextphoto']['id']!='0'
531
+ return [@previousPhoto, @nextPhoto]
532
+ end
533
+
534
+ # Implements flickr.photos.getExif
535
+ def exif
536
+ @client.photos_getExif('photo_id'=>@id)['photo']
537
+ end
538
+
539
+ # Implements flickr.photos.getPerms
540
+ def permissions
541
+ @client.photos_getPerms('photo_id'=>@id)['perms']
542
+ end
543
+
544
+ # Implements flickr.photos.getSizes
545
+ def sizes(size=nil)
546
+ size = normalize_size(size)
547
+ sizes = @client.photos_getSizes('photo_id'=>@id)['sizes']['size']
548
+ sizes = sizes.find{|asize| asize['label']==size} if size
549
+ return sizes
550
+ end
551
+
552
+ # flickr.tags.getListPhoto
553
+ def tags
554
+ @client.tags_getListPhoto('photo_id'=>@id)['photo']['tags']
555
+ end
556
+
557
+ # Implements flickr.photos.notes.add
558
+ def add_note(note)
559
+ end
560
+
561
+ # Implements flickr.photos.setDates
562
+ def dates=(dates)
563
+ end
564
+
565
+ # Implements flickr.photos.setPerms
566
+ def perms=(perms)
567
+ end
568
+
569
+ # Implements flickr.photos.setTags
570
+ def tags=(tags)
571
+ end
572
+
573
+ # Implements flickr.photos.setMeta
574
+ def title=(title)
575
+ end
576
+ def description=(title)
577
+ end
578
+
579
+ # Implements flickr.photos.addTags
580
+ def add_tag(tag)
581
+ end
582
+
583
+ # Implements flickr.photos.removeTag
584
+ def remove_tag(tag)
585
+ end
586
+
587
+ # Implements flickr.photos.transform.rotate
588
+ def rotate
589
+ end
590
+
591
+ # Implements flickr.blogs.postPhoto
592
+ def postToBlog(blog_id, title='', description='')
593
+ @client.blogs_postPhoto('photo_id'=>@id, 'title'=>title, 'description'=>description)
594
+ end
595
+
596
+ # Implements flickr.photos.notes.delete
597
+ def deleteNote(note_id)
598
+ end
599
+
600
+ # Implements flickr.photos.notes.edit
601
+ def editNote(note_id)
602
+ end
603
+
604
+ # Converts the Photo to a string by returning its title
605
+ def to_s
606
+ title
607
+ end
608
+
609
+ private
610
+
611
+ # Implements flickr.photos.getInfo
612
+ def getInfo(attrib="")
613
+ return instance_variable_get("@#{attrib}") if @got_info
614
+ info = @client.photos_getInfo('photo_id'=>@id)['photo']
615
+ @got_info = true
616
+ info.each { |k,v| instance_variable_set("@#{k}", v)}
617
+ @owner = User.new(info['owner']['nsid'], info['owner']['username'], nil, nil, @api_key)
618
+ @tags = info['tags']['tag']
619
+ @notes = info['notes']['note']#.collect { |note| Note.new(note.id) }
620
+ @url = info['urls']['url']['content'] # assumes only one url
621
+ instance_variable_get("@#{attrib}")
622
+ end
623
+
624
+ # Builds source uri of image from params (often returned from other
625
+ # methods, e.g. User#photos). As specified at:
626
+ # http://www.flickr.com/services/api/misc.urls.html. If size is given
627
+ # should be one the keys in the VALID_SIZES hash, i.e.
628
+ # "Square", "Thumbnail", "Medium", "Large", "Original", "Small" (These
629
+ # are the values returned by flickr.photos.getSizes).
630
+ # If no size is given the uri for "Medium"-size image, i.e. with width
631
+ # of 500 is returned
632
+ # TODO: Handle "Original" size
633
+ def image_source_uri_from_self(size=nil)
634
+ return unless @farm&&@server&&@id&&@secret
635
+ s_size = VALID_SIZES[normalize_size(size)] # get the short letters array corresponding to the size
636
+ s_size = s_size&&s_size[0] # the first element of this array is used to build the source uri
637
+ if s_size.nil?
638
+ "http://farm#{@farm}.static.flickr.com/#{@server}/#{@id}_#{@secret}.jpg"
639
+ else
640
+ "http://farm#{@farm}.static.flickr.com/#{@server}/#{@id}_#{@secret}_#{s_size}.jpg"
641
+ end
642
+ end
643
+
644
+ # Builds uri of Flickr page for photo. By default returns the main
645
+ # page for the photo, but if passed a size will return the simplified
646
+ # flickr page featuring the given size of the photo
647
+ # TODO: Handle "Original" size
648
+ def uri_for_photo_from_self(size=nil)
649
+ return unless @owner&&@id
650
+ size = normalize_size(size)
651
+ s_size = VALID_SIZES[size] # get the short letters array corresponding to the size
652
+ s_size = s_size&&s_size[1] # the second element of this array is used to build the uri of the flickr page for this size
653
+ "http://www.flickr.com/photos/#{owner.id}/#{@id}" + (s_size ? "/sizes/#{s_size}/" : "")
654
+ end
655
+ end
656
+
657
+ # Todo:
658
+ # flickr.groups.pools.add
659
+ # flickr.groups.pools.getContext
660
+ # flickr.groups.pools.getGroups
661
+ # flickr.groups.pools.getPhotos
662
+ # flickr.groups.pools.remove
663
+ class Group
664
+ attr_reader :id, :client, :description, :name, :eighteenplus, :members, :online, :privacy, :url#, :chatid, :chatcount
665
+
666
+ def initialize(id_or_params_hash=nil, api_key=nil)
667
+ if id_or_params_hash.is_a?(Hash)
668
+ id_or_params_hash.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
669
+ else
670
+ @id = id_or_params_hash
671
+ @api_key = api_key
672
+ @client = Flickr.new @api_key
673
+ end
674
+ end
675
+
676
+ # Implements flickr.groups.getInfo and flickr.urls.getGroup
677
+ # private, once we can call it as needed
678
+ def getInfo
679
+ info = @client.groups_getInfo('group_id'=>@id)['group']
680
+ @name = info['name']
681
+ @members = info['members']
682
+ @online = info['online']
683
+ @privacy = info['privacy']
684
+ # @chatid = info['chatid']
685
+ # @chatcount = info['chatcount']
686
+ @url = @client.urls_getGroup('group_id'=>@id)['group']['url']
687
+ self
688
+ end
689
+
690
+ end
691
+
692
+ # Todo:
693
+ # flickr.photosets.delete
694
+ # flickr.photosets.editMeta
695
+ # flickr.photosets.editPhotos
696
+ # flickr.photosets.getContext
697
+ # flickr.photosets.getInfo
698
+ # flickr.photosets.getPhotos
699
+ class Photoset
700
+
701
+ attr_reader :id, :client, :owner, :primary, :photos, :title, :description, :url
702
+
703
+ def initialize(id=nil, api_key=nil)
704
+ @id = id
705
+ @api_key = api_key
706
+ @client = Flickr.new @api_key
707
+ end
708
+
709
+ # Implements flickr.photosets.getInfo
710
+ # private, once we can call it as needed
711
+ def getInfo
712
+ info = @client.photosets_getInfo('photoset_id'=>@id)['photoset']
713
+ @owner = User.new(info['owner'], nil, nil, nil, @api_key)
714
+ @primary = info['primary']
715
+ @photos = info['photos']
716
+ @title = info['title']
717
+ @description = info['description']
718
+ @url = "http://www.flickr.com/photos/#{@owner.getInfo.username}/sets/#{@id}/"
719
+ self
720
+ end
721
+
722
+ def getPhotos
723
+ photosetPhotos = @client.request('photosets.getPhotos', {'photoset_id' => @id})
724
+
725
+ collection = []
726
+ photosetPhotos['photoset']['photo'].each { |photo| collection << Photo.new(photo, @api_key) }
727
+ collection
728
+ end
729
+
730
+ end
731
+
732
+ end