unimatrixZxero-flickr 1.0.8

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