andrewtimberlake-flickr 1.0.5

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 +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