andrewtimberlake-flickr 1.0.5

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 +73 -0
  4. data/TODO +6 -0
  5. data/lib/flickr.rb +695 -0
  6. data/test/test_flickr.rb +1082 -0
  7. metadata +66 -0
@@ -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.
@@ -0,0 +1,73 @@
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 # ...and their tags
27
+ recentphotos = flickr.photos # get the 100 most recent public photos
28
+ photo = recentphotos.first # or very most recent one
29
+ photo.url # see its URL,
30
+ photo.title # title,
31
+ photo.description # and description,
32
+ photo.owner # and its owner.
33
+ File.open(photo.filename, 'w') do |file|
34
+ file.puts p.file # save the photo to a local file
35
+ end
36
+ flickr.photos.each do |p| # get the last 100 public photos...
37
+ File.open(p.filename, 'w') do |f|
38
+ f.puts p.file('Square') # ...and save a local copy of their square thumbnail
39
+ end
40
+ end
41
+
42
+ == REQUIREMENTS:
43
+
44
+ * Xmlsimple gem
45
+
46
+ == INSTALL:
47
+
48
+ * sudo gem install flickr
49
+
50
+ == LICENSE:
51
+
52
+ (The MIT License)
53
+
54
+ Copyright (c) 2008 Scott Raymond, Patrick Plattes, Chris Taggart
55
+
56
+ Permission is hereby granted, free of charge, to any person obtaining
57
+ a copy of this software and associated documentation files (the
58
+ 'Software'), to deal in the Software without restriction, including
59
+ without limitation the rights to use, copy, modify, merge, publish,
60
+ distribute, sublicense, and/or sell copies of the Software, and to
61
+ permit persons to whom the Software is furnished to do so, subject to
62
+ the following conditions:
63
+
64
+ The above copyright notice and this permission notice shall be
65
+ included in all copies or substantial portions of the Software.
66
+
67
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
68
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
69
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
70
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
71
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
72
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
73
+ 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
@@ -0,0 +1,695 @@
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
+ def users(lookup=nil)
144
+ user = people_findByEmail('find_email'=>lookup)['user'] rescue people_findByUsername('username'=>lookup)['user']
145
+ return User.new("id" => user["nsid"], "username" => user["username"], "client" => self)
146
+ end
147
+
148
+ # Implements flickr.groups.search
149
+ def groups(group_name, options={})
150
+ collection = groups_search({"text" => group_name}.merge(options))['groups']['group']
151
+ collection = [collection] if collection.is_a? Hash
152
+
153
+ collection.collect { |group| Group.new( "id" => group['nsid'],
154
+ "name" => group['name'],
155
+ "eighteenplus" => group['eighteenplus'],
156
+ "client" => self) }
157
+ end
158
+
159
+ # Implements flickr.tags.getRelated
160
+ def related_tags(tag)
161
+ tags_getRelated('tag'=>tag)['tags']['tag']
162
+ end
163
+
164
+ # Implements flickr.photos.licenses.getInfo
165
+ def licenses
166
+ photos_licenses_getInfo['licenses']['license']
167
+ end
168
+
169
+ # Returns url for user to login in to Flickr to authenticate app for a user
170
+ def login_url(perms)
171
+ "http://flickr.com/services/auth/?api_key=#{@api_key}&perms=#{perms}&api_sig=#{signature_from('api_key'=>@api_key, 'perms' => perms)}"
172
+ end
173
+
174
+ # Implements everything else.
175
+ # Any method not defined explicitly will be passed on to the Flickr API,
176
+ # and return an XmlSimple document. For example, Flickr#test_echo is not
177
+ # defined, so it will pass the call to the flickr.test.echo method.
178
+ def method_missing(method_id, params={})
179
+ request(method_id.id2name.gsub(/_/, '.'), params)
180
+ end
181
+
182
+ # Does an HTTP GET on a given URL and returns the response body
183
+ def http_get(url)
184
+ Net::HTTP.get_response(URI.parse(url)).body.to_s
185
+ end
186
+
187
+ # Takes a Flickr API method name and set of parameters; returns an XmlSimple object with the response
188
+ def request(method, params={})
189
+ url = request_url(method, params)
190
+ response = XmlSimple.xml_in(http_get(url), { 'ForceArray' => false })
191
+ raise response['err']['msg'] if response['stat'] != 'ok'
192
+ response
193
+ end
194
+
195
+ # acts like request but returns a PhotoCollection (a list of Photo objects)
196
+ def photos_request(method, params={})
197
+ photos = request(method, params)
198
+ PhotoCollection.new(photos, @api_key)
199
+ end
200
+
201
+ # Builds url for Flickr API REST request from given the flickr method name
202
+ # (exclusing the 'flickr.' that begins each method call) and params (where
203
+ # applicable) which should be supplied as a Hash (e.g 'user_id' => "foo123")
204
+ def request_url(method, params={})
205
+ method = 'flickr.' + method
206
+ url = "#{@host}#{@api}/?api_key=#{@api_key}&method=#{method}"
207
+ params.merge!('api_key' => @api_key, 'method' => method, 'auth_token' => @auth_token)
208
+ signature = signature_from(params)
209
+
210
+ url = "#{@host}#{@api}/?" + params.merge('api_sig' => signature).collect { |k,v| "#{k}=" + CGI::escape(v.to_s) unless v.nil? }.compact.join("&")
211
+ end
212
+
213
+ def signature_from(params={})
214
+ return unless @shared_secret # don't both getting signature if no shared_secret
215
+ 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
216
+ return Digest::MD5.hexdigest("#{@shared_secret}#{request_str}")
217
+ end
218
+
219
+ # A collection of photos is returned as a PhotoCollection, a subclass of Array.
220
+ # This allows us to retain the pagination info returned by Flickr and make it
221
+ # accessible in a friendly way
222
+ class PhotoCollection < Array
223
+ attr_reader :page, :pages, :perpage, :total
224
+
225
+ # builds a PhotoCollection from given params, such as those returned from
226
+ # photos.search API call
227
+ def initialize(photos_api_response={}, api_key=nil)
228
+ [ "page", "pages", "perpage", "total" ].each { |i| instance_variable_set("@#{i}", photos_api_response["photos"][i])}
229
+ collection = photos_api_response['photos']['photo'] || []
230
+ collection = [collection] if collection.is_a? Hash
231
+ collection.each { |photo| self << Photo.new(photo.delete('id'), api_key, photo) }
232
+ end
233
+ end
234
+
235
+ # Todo:
236
+ # logged_in?
237
+ # if logged in:
238
+ # flickr.blogs.getList
239
+ # flickr.favorites.add
240
+ # flickr.favorites.remove
241
+ # flickr.groups.browse
242
+ # flickr.photos.getCounts
243
+ # flickr.photos.getNotInSet
244
+ # flickr.photos.getUntagged
245
+ # flickr.photosets.create
246
+ # flickr.photosets.orderSets
247
+ # flickr.tags.getListUserPopular
248
+ # flickr.test.login
249
+ # uploading
250
+ class User
251
+
252
+ attr_reader :client, :id, :name, :location, :photos_url, :url, :count, :firstdate, :firstdatetaken
253
+
254
+ # A Flickr::User can be instantiated in two ways. The old (deprecated)
255
+ # method is with an ordered series of values. The new method is with a
256
+ # params Hash, which is easier when a variable number of params are
257
+ # supplied, which is the case here, and also avoids having to constantly
258
+ # supply nil values for the email and password, which are now irrelevant
259
+ # as authentication is no longer done this way.
260
+ # An associated flickr client will also be generated if an api key is
261
+ # passed among the arguments or in the params hash. Alternatively, and
262
+ # most likely, an existing client object may be passed in the params hash
263
+ # (e.g. 'client' => some_existing_flickr_client_object), and this is
264
+ # what happends when users are initlialized as the result of a method
265
+ # called on the flickr client (e.g. flickr.users)
266
+ def initialize(id_or_params_hash=nil, username=nil, email=nil, password=nil, api_key=nil)
267
+ if id_or_params_hash.is_a?(Hash)
268
+ id_or_params_hash.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
269
+ else
270
+ @id = id_or_params_hash
271
+ @username = username
272
+ @email = email
273
+ @password = password
274
+ @api_key = api_key
275
+ end
276
+ @client ||= Flickr.new('api_key' => @api_key, 'shared_secret' => @shared_secret, 'auth_token' => @auth_token) if @api_key
277
+ @client.login(@email, @password) if @email and @password # this is now irrelevant as Flickr API no longer supports authentication this way
278
+ end
279
+
280
+ def username
281
+ @username.nil? ? getInfo.username : @username
282
+ end
283
+ def name
284
+ @name.nil? ? getInfo.name : @name
285
+ end
286
+ def location
287
+ @location.nil? ? getInfo.location : @location
288
+ end
289
+ def count
290
+ @count.nil? ? getInfo.count : @count
291
+ end
292
+ def firstdate
293
+ @firstdate.nil? ? getInfo.firstdate : @firstdate
294
+ end
295
+ def firstdatetaken
296
+ @firstdatetaken.nil? ? getInfo.firstdatetaken : @firstdatetaken
297
+ end
298
+
299
+ # Builds url for user's photos page as per
300
+ # http://www.flickr.com/services/api/misc.urls.html
301
+ def photos_url
302
+ "http://www.flickr.com/photos/#{id}/"
303
+ end
304
+
305
+ # Builds url for user's profile page as per
306
+ # http://www.flickr.com/services/api/misc.urls.html
307
+ def url
308
+ "http://www.flickr.com/people/#{id}/"
309
+ end
310
+
311
+ def pretty_url
312
+ @pretty_url ||= @client.urls_getUserProfile('user_id'=>@id)['user']['url']
313
+ end
314
+
315
+ # Implements flickr.people.getPublicGroups
316
+ def groups
317
+ collection = @client.people_getPublicGroups('user_id'=>@id)['groups']['group']
318
+ collection = [collection] if collection.is_a? Hash
319
+ collection.collect { |group| Group.new( "id" => group['nsid'],
320
+ "name" => group['name'],
321
+ "eighteenplus" => group['eighteenplus'],
322
+ "client" => @client) }
323
+ end
324
+
325
+ # Implements flickr.people.getPublicPhotos. Options hash allows you to add
326
+ # extra restrictions as per flickr.people.getPublicPhotos docs, e.g.
327
+ # user.photos('per_page' => '25', 'extras' => 'date_taken')
328
+ def photos(options={})
329
+ @client.photos_request('people.getPublicPhotos', {'user_id' => @id}.merge(options))
330
+ # what about non-public photos?
331
+ end
332
+
333
+ # Gets photos with a given tag
334
+ def tag(tag)
335
+ @client.photos('user_id'=>@id, 'tags'=>tag)
336
+ end
337
+
338
+ # Implements flickr.contacts.getPublicList and flickr.contacts.getList
339
+ def contacts
340
+ @client.contacts_getPublicList('user_id'=>@id)['contacts']['contact'].collect { |contact| User.new(contact['nsid'], contact['username'], nil, nil, @api_key) }
341
+ #or
342
+ end
343
+
344
+ # Implements flickr.favorites.getPublicList
345
+ def favorites
346
+ @client.photos_request('favorites.getPublicList', 'user_id' => @id)
347
+ end
348
+
349
+ # Implements flickr.photosets.getList
350
+ def photosets
351
+ @client.photosets_getList('user_id'=>@id)['photosets']['photoset'].collect { |photoset| Photoset.new(photoset['id'], @api_key) }
352
+ end
353
+
354
+ # Implements flickr.tags.getListUser
355
+ def tags
356
+ @client.tags_getListUser('user_id'=>@id)['who']['tags']['tag'].collect { |tag| tag }
357
+ end
358
+
359
+ # Implements flickr.photos.getContactsPublicPhotos and flickr.photos.getContactsPhotos
360
+ def contactsPhotos
361
+ @client.photos_request('photos.getContactsPublicPhotos', 'user_id' => @id)
362
+ end
363
+
364
+ def to_s
365
+ @name
366
+ end
367
+
368
+ private
369
+
370
+ # Implements flickr.people.getInfo, flickr.urls.getUserPhotos, and flickr.urls.getUserProfile
371
+ def getInfo
372
+ info = @client.people_getInfo('user_id'=>@id)['person']
373
+ @username = info['username']
374
+ @name = info['realname']
375
+ @location = info['location']
376
+ @count = info['photos']['count']
377
+ @firstdate = info['photos']['firstdate']
378
+ @firstdatetaken = info['photos']['firstdatetaken']
379
+ self
380
+ end
381
+
382
+ end
383
+
384
+ class Photo
385
+
386
+ attr_reader :id, :client, :title
387
+
388
+ def initialize(id=nil, api_key=nil, extra_params={})
389
+ @id = id
390
+ @api_key = api_key
391
+ extra_params.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
392
+ @client = Flickr.new @api_key
393
+ end
394
+
395
+ # Allows access to all photos instance variables through hash like
396
+ # interface, e.g. photo["datetaken"] returns @datetaken instance
397
+ # variable. Useful for accessing any weird and wonderful parameter
398
+ # that may have been returned by Flickr when finding the photo,
399
+ # e.g. those returned by the extras argument in
400
+ # flickr.people.getPublicPhotos
401
+ def [](param_name)
402
+ instance_variable_get("@#{param_name}")
403
+ end
404
+
405
+ def title
406
+ @title.nil? ? getInfo("title") : @title
407
+ end
408
+
409
+ # Returns the owner of the photo as a Flickr::User. If we have no info
410
+ # about the owner, we make an API call to get it. If we already have
411
+ # the owner's id, create a user based on that. Either way, we cache the
412
+ # result so we don't need to check again
413
+ def owner
414
+ case @owner
415
+ when Flickr::User
416
+ @owner
417
+ when String
418
+ @owner = Flickr::User.new(@owner, nil, nil, nil, @api_key)
419
+ else
420
+ getInfo("owner")
421
+ end
422
+ end
423
+
424
+ def server
425
+ @server.nil? ? getInfo("server") : @server
426
+ end
427
+
428
+ def isfavorite
429
+ @isfavorite.nil? ? getInfo("isfavorite") : @isfavorite
430
+ end
431
+
432
+ def license
433
+ @license.nil? ? getInfo("license") : @license
434
+ end
435
+
436
+ def rotation
437
+ @rotation.nil? ? getInfo("rotation") : @rotation
438
+ end
439
+
440
+ def description
441
+ @description || getInfo("description")
442
+ end
443
+
444
+ def notes
445
+ @notes.nil? ? getInfo("notes") : @notes
446
+ end
447
+
448
+ # Returns the URL for the photo size page
449
+ # defaults to 'Medium'
450
+ # other valid sizes are in the VALID_SIZES hash
451
+ def size_url(size='Medium')
452
+ uri_for_photo_from_self(size) || sizes(size)['url']
453
+ end
454
+
455
+ # converts string or symbol size to a capitalized string
456
+ def normalize_size(size)
457
+ size ? size.to_s.capitalize : size
458
+ end
459
+
460
+ # the URL for the main photo page
461
+ # if getInfo has already been called, this will return the pretty url
462
+ #
463
+ # for historical reasons, an optional size can be given
464
+ # 'Medium' returns the regular url; any other size returns a size page
465
+ # use size_url instead
466
+ def url(size = nil)
467
+ if normalize_size(size) != 'Medium'
468
+ size_url(size)
469
+ else
470
+ @url || uri_for_photo_from_self
471
+ end
472
+ end
473
+
474
+ # the 'pretty' url for a photo
475
+ # (if the user has set up a custom name)
476
+ # eg, http://flickr.com/photos/granth/2584402507/ instead of
477
+ # http://flickr.com/photos/23386158@N00/2584402507/
478
+ def pretty_url
479
+ @url || getInfo("pretty_url")
480
+ end
481
+
482
+ # Returns the URL for the image (default or any specified size)
483
+ def source(size='Medium')
484
+ image_source_uri_from_self(size) || sizes(size)['source']
485
+ end
486
+
487
+ # Returns the photo file data itself, in any specified size. Example: File.open(photo.title, 'w') { |f| f.puts photo.file }
488
+ def file(size='Medium')
489
+ Net::HTTP.get_response(URI.parse(source(size))).body
490
+ end
491
+
492
+ # Unique filename for the image, based on the Flickr NSID
493
+ def filename
494
+ "#{@id}.jpg"
495
+ end
496
+
497
+ # Implements flickr.photos.getContext
498
+ def context
499
+ context = @client.photos_getContext('photo_id'=>@id)
500
+ @previousPhoto = Photo.new(context['prevphoto'].delete('id'), @api_key, context['prevphoto']) if context['prevphoto']['id']!='0'
501
+ @nextPhoto = Photo.new(context['nextphoto'].delete('id'), @api_key, context['nextphoto']) if context['nextphoto']['id']!='0'
502
+ return [@previousPhoto, @nextPhoto]
503
+ end
504
+
505
+ # Implements flickr.photos.getExif
506
+ def exif
507
+ @client.photos_getExif('photo_id'=>@id)['photo']
508
+ end
509
+
510
+ # Implements flickr.photos.getPerms
511
+ def permissions
512
+ @client.photos_getPerms('photo_id'=>@id)['perms']
513
+ end
514
+
515
+ # Implements flickr.photos.getSizes
516
+ def sizes(size=nil)
517
+ size = normalize_size(size)
518
+ sizes = @client.photos_getSizes('photo_id'=>@id)['sizes']['size']
519
+ sizes = sizes.find{|asize| asize['label']==size} if size
520
+ return sizes
521
+ end
522
+
523
+ # flickr.tags.getListPhoto
524
+ def tags
525
+ @client.tags_getListPhoto('photo_id'=>@id)['photo']['tags']
526
+ end
527
+
528
+ # Implements flickr.photos.notes.add
529
+ def add_note(note)
530
+ end
531
+
532
+ # Implements flickr.photos.setDates
533
+ def dates=(dates)
534
+ end
535
+
536
+ # Implements flickr.photos.setPerms
537
+ def perms=(perms)
538
+ end
539
+
540
+ # Implements flickr.photos.setTags
541
+ def tags=(tags)
542
+ end
543
+
544
+ # Implements flickr.photos.setMeta
545
+ def title=(title)
546
+ end
547
+ def description=(title)
548
+ end
549
+
550
+ # Implements flickr.photos.addTags
551
+ def add_tag(tag)
552
+ end
553
+
554
+ # Implements flickr.photos.removeTag
555
+ def remove_tag(tag)
556
+ end
557
+
558
+ # Implements flickr.photos.transform.rotate
559
+ def rotate
560
+ end
561
+
562
+ # Implements flickr.blogs.postPhoto
563
+ def postToBlog(blog_id, title='', description='')
564
+ @client.blogs_postPhoto('photo_id'=>@id, 'title'=>title, 'description'=>description)
565
+ end
566
+
567
+ # Implements flickr.photos.notes.delete
568
+ def deleteNote(note_id)
569
+ end
570
+
571
+ # Implements flickr.photos.notes.edit
572
+ def editNote(note_id)
573
+ end
574
+
575
+ # Converts the Photo to a string by returning its title
576
+ def to_s
577
+ title
578
+ end
579
+
580
+ private
581
+
582
+ # Implements flickr.photos.getInfo
583
+ def getInfo(attrib="")
584
+ return instance_variable_get("@#{attrib}") if @got_info
585
+ info = @client.photos_getInfo('photo_id'=>@id)['photo']
586
+ @got_info = true
587
+ info.each { |k,v| instance_variable_set("@#{k}", v)}
588
+ @owner = User.new(info['owner']['nsid'], info['owner']['username'], nil, nil, @api_key)
589
+ @tags = info['tags']['tag']
590
+ @notes = info['notes']['note']#.collect { |note| Note.new(note.id) }
591
+ @url = info['urls']['url']['content'] # assumes only one url
592
+ instance_variable_get("@#{attrib}")
593
+ end
594
+
595
+ # Builds source uri of image from params (often returned from other
596
+ # methods, e.g. User#photos). As specified at:
597
+ # http://www.flickr.com/services/api/misc.urls.html. If size is given
598
+ # should be one the keys in the VALID_SIZES hash, i.e.
599
+ # "Square", "Thumbnail", "Medium", "Large", "Original", "Small" (These
600
+ # are the values returned by flickr.photos.getSizes).
601
+ # If no size is given the uri for "Medium"-size image, i.e. with width
602
+ # of 500 is returned
603
+ # TODO: Handle "Original" size
604
+ def image_source_uri_from_self(size=nil)
605
+ return unless @farm&&@server&&@id&&@secret
606
+ s_size = VALID_SIZES[normalize_size(size)] # get the short letters array corresponding to the size
607
+ s_size = s_size&&s_size[0] # the first element of this array is used to build the source uri
608
+ if s_size.nil?
609
+ "http://farm#{@farm}.static.flickr.com/#{@server}/#{@id}_#{@secret}.jpg"
610
+ else
611
+ "http://farm#{@farm}.static.flickr.com/#{@server}/#{@id}_#{@secret}_#{s_size}.jpg"
612
+ end
613
+ end
614
+
615
+ # Builds uri of Flickr page for photo. By default returns the main
616
+ # page for the photo, but if passed a size will return the simplified
617
+ # flickr page featuring the given size of the photo
618
+ # TODO: Handle "Original" size
619
+ def uri_for_photo_from_self(size=nil)
620
+ return unless @owner&&@id
621
+ size = normalize_size(size)
622
+ s_size = VALID_SIZES[size] # get the short letters array corresponding to the size
623
+ 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
624
+ "http://www.flickr.com/photos/#{owner.id}/#{@id}" + (s_size ? "/sizes/#{s_size}/" : "")
625
+ end
626
+ end
627
+
628
+ # Todo:
629
+ # flickr.groups.pools.add
630
+ # flickr.groups.pools.getContext
631
+ # flickr.groups.pools.getGroups
632
+ # flickr.groups.pools.getPhotos
633
+ # flickr.groups.pools.remove
634
+ class Group
635
+ attr_reader :id, :client, :description, :name, :eighteenplus, :members, :online, :privacy, :url#, :chatid, :chatcount
636
+
637
+ def initialize(id_or_params_hash=nil, api_key=nil)
638
+ if id_or_params_hash.is_a?(Hash)
639
+ id_or_params_hash.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
640
+ else
641
+ @id = id_or_params_hash
642
+ @api_key = api_key
643
+ @client = Flickr.new @api_key
644
+ end
645
+ end
646
+
647
+ # Implements flickr.groups.getInfo and flickr.urls.getGroup
648
+ # private, once we can call it as needed
649
+ def getInfo
650
+ info = @client.groups_getInfo('group_id'=>@id)['group']
651
+ @name = info['name']
652
+ @members = info['members']
653
+ @online = info['online']
654
+ @privacy = info['privacy']
655
+ # @chatid = info['chatid']
656
+ # @chatcount = info['chatcount']
657
+ @url = @client.urls_getGroup('group_id'=>@id)['group']['url']
658
+ self
659
+ end
660
+
661
+ end
662
+
663
+ # Todo:
664
+ # flickr.photosets.delete
665
+ # flickr.photosets.editMeta
666
+ # flickr.photosets.editPhotos
667
+ # flickr.photosets.getContext
668
+ # flickr.photosets.getInfo
669
+ # flickr.photosets.getPhotos
670
+ class Photoset
671
+
672
+ attr_reader :id, :client, :owner, :primary, :photos, :title, :description, :url
673
+
674
+ def initialize(id=nil, api_key=nil)
675
+ @id = id
676
+ @api_key = api_key
677
+ @client = Flickr.new @api_key
678
+ end
679
+
680
+ # Implements flickr.photosets.getInfo
681
+ # private, once we can call it as needed
682
+ def getInfo
683
+ info = @client.photosets_getInfo('photoset_id'=>@id)['photoset']
684
+ @owner = User.new(info['owner'], nil, nil, nil, @api_key)
685
+ @primary = info['primary']
686
+ @photos = info['photos']
687
+ @title = info['title']
688
+ @description = info['description']
689
+ @url = "http://www.flickr.com/photos/#{@owner.getInfo.username}/sets/#{@id}/"
690
+ self
691
+ end
692
+
693
+ end
694
+
695
+ end