casademora-flickr 1.0.6

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