ctagg-flickr 1.0.8

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 +59 -0
  2. data/LICENSE +20 -0
  3. data/README.txt +74 -0
  4. data/TODO +6 -0
  5. data/lib/flickr.rb +711 -0
  6. data/test/test_flickr.rb +1122 -0
  7. metadata +66 -0
data/History.txt ADDED
@@ -0,0 +1,59 @@
1
+ == 1.0.8 2009-01-25
2
+ * 2 minor enhancements
3
+ * Refactored initialization of PhotoCollection so can be instantiated from response returned by photosets.getPhotos
4
+ * Added photosets.getPhotos. Returns PhotoCollection
5
+
6
+ == 1.0.7 2008-11-26
7
+ * 1 major enhancement
8
+ * When a collection of photos is fetched (e.g. by Flickr#search), a PhotoCollection object is now returned, a type of array, which allows easy access to the pagination information returned by Flickr (e.g. total items, pages, etc). Previously this info was lost
9
+ * Minor enhancements:
10
+ * Refactored parsing of response when getting info about photo (public API is not changed).
11
+ * All returned info is now parsed and stored (previously only some attributes were)
12
+ * A record is kept of whether info has been previously fetched from Flickr (avoids unnecessary extra calls)
13
+ * Improved User#getInfo. No longer makes API call for pretty_url (which was in addition to main getInfo call) or photos_url
14
+ * Bugfixes
15
+ * Fixed Photo#source not to make unnecessary API call
16
+ * Fixed User#url not to make unnecessary API call
17
+
18
+ == 1.0.6 2008-07-30
19
+ * Bugfixes:
20
+ * fixed Flickr#photos when used for searching
21
+ * fixed Flickr::Photo#url to not use usernames (if the user changes their username, the url will wrong)
22
+ * fixed Flickr#related_tags
23
+ * Flickr::Photo#to_s was making unnecessary API calls
24
+ * fixed 'test' Rake task
25
+ * fixed 'gem' Rake task
26
+ * Minor enhancements:
27
+ * added Flickr#search as an alias for Flickr#photos in its search mode
28
+ * added Flickr#recent as an alias for Flickr#photos for recent photos
29
+ * added Flickr::Photo#pretty_url for the URL that includes a username if present
30
+ * added Flickr::Photo#size_url which is like url except 'Medium' works the same as other sizes
31
+ * allow lowercase ('medium') and symbol (:medium) forms for sizes
32
+ * internal code cleanup
33
+
34
+ == 1.0.5 2008-05-12
35
+
36
+ * 1 major change:
37
+ * Updated and refactored Flickr::Group class and Flickr#groups method to work with current Flickr API. Flickr#groups now searches for given group, rather than groups.getActiveList (which no longer exists as Flickr API call)
38
+ * Minor enhancements:
39
+ * Tweaked internals so new client instance isn't created each time new object (e.g. photo, user) is created
40
+ * Improved test coverage
41
+
42
+ == 1.0.4 2008-05-11
43
+
44
+ * 1 major enhancement:
45
+ * Added authentication facility as per current Flickr API
46
+ * 3 minor enhancements:
47
+ * Improved test suite
48
+ * Improved API for creating Flickr objects (old one still works)
49
+ * Protected methods that are only used internally (e.g. request, request_url)
50
+
51
+ == 1.0.3 2008-04-18
52
+
53
+ * Various bugfixes:
54
+ * User instantiation was broken (wrong number of params passed)
55
+ * Instantiating photos failed in several places if single photo returned
56
+ * Photosets#getInfo would always fail as the parameter name should be photoset_id, not photosets_id
57
+ * Removed call to flickr.people.getOnlineList in Flickr#users as that call is no longer in the Flickr API
58
+ * Performance improvements:
59
+ * Url and image source uri now generated without a call to the Flickr API, as per the Flickr API docs
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Scott Raymond, Patrick Plattes, Chris Taggart
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.txt ADDED
@@ -0,0 +1,74 @@
1
+ = flickr
2
+
3
+ http://github.com/ctagg/flickr
4
+
5
+ == DESCRIPTION:
6
+
7
+ An insanely easy interface to the Flickr photo-sharing service. By Scott Raymond. (& updated May 08 by Chris Taggart, http://pushrod.wordpress.com)
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ The flickr gem (famously featured in a RubyonRails screencast) had broken with Flickr's new authentication scheme and updated API.
12
+ This has now been largely corrected, though not all current API calls are supported yet.
13
+
14
+ == SYNOPSIS:
15
+
16
+ require 'flickr'
17
+ flickr = Flickr.new('some_flickr_api_key') # create a flickr client (get an API key from http://www.flickr.com/services/api/)
18
+ user = flickr.users('sco@scottraymond.net') # lookup a user
19
+ user.name # get the user's name
20
+ user.location # and location
21
+ user.photos # grab their collection of Photo objects...
22
+ user.groups # ...the groups they're in...
23
+ user.contacts # ...their contacts...
24
+ user.favorites # ...favorite photos...
25
+ user.photosets # ...their photo sets...
26
+ user.tags # ...their tags...
27
+ user.popular_tags # ...and their popular tags
28
+ recentphotos = flickr.photos # get the 100 most recent public photos
29
+ photo = recentphotos.first # or very most recent one
30
+ photo.url # see its URL,
31
+ photo.title # title,
32
+ photo.description # and description,
33
+ photo.owner # and its owner.
34
+ File.open(photo.filename, 'w') do |file|
35
+ file.puts p.file # save the photo to a local file
36
+ end
37
+ flickr.photos.each do |p| # get the last 100 public photos...
38
+ File.open(p.filename, 'w') do |f|
39
+ f.puts p.file('Square') # ...and save a local copy of their square thumbnail
40
+ end
41
+ end
42
+
43
+ == REQUIREMENTS:
44
+
45
+ * Xmlsimple gem
46
+
47
+ == INSTALL:
48
+
49
+ * sudo gem install flickr
50
+
51
+ == LICENSE:
52
+
53
+ (The MIT License)
54
+
55
+ Copyright (c) 2008 Scott Raymond, Patrick Plattes, Chris Taggart
56
+
57
+ Permission is hereby granted, free of charge, to any person obtaining
58
+ a copy of this software and associated documentation files (the
59
+ 'Software'), to deal in the Software without restriction, including
60
+ without limitation the rights to use, copy, modify, merge, publish,
61
+ distribute, sublicense, and/or sell copies of the Software, and to
62
+ permit persons to whom the Software is furnished to do so, subject to
63
+ the following conditions:
64
+
65
+ The above copyright notice and this permission notice shall be
66
+ included in all copies or substantial portions of the Software.
67
+
68
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
69
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
70
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
71
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
72
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
73
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
74
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/TODO ADDED
@@ -0,0 +1,6 @@
1
+ TODO:
2
+ Complete test suite for untested methods
3
+ Bring outdated methods up-to-date
4
+ Write documentation
5
+ Add missing Flickr API call
6
+ Convert dates to ruby dates
data/lib/flickr.rb ADDED
@@ -0,0 +1,711 @@
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. Note all the info is contained in the value of
231
+ # the first (and only) key-value pair of the response. The key will vary
232
+ # depending on the original object the photos are related to (e.g 'photos',
233
+ # 'photoset', etc)
234
+ def initialize(photos_api_response={}, api_key=nil)
235
+ photos = photos_api_response.values.first
236
+ [ "page", "pages", "perpage", "total" ].each { |i| instance_variable_set("@#{i}", photos[i])}
237
+ collection = photos['photo'] || []
238
+ collection = [collection] if collection.is_a? Hash
239
+ collection.each { |photo| self << Photo.new(photo.delete('id'), api_key, photo) }
240
+ end
241
+ end
242
+
243
+ # Todo:
244
+ # logged_in?
245
+ # if logged in:
246
+ # flickr.blogs.getList
247
+ # flickr.favorites.add
248
+ # flickr.favorites.remove
249
+ # flickr.groups.browse
250
+ # flickr.photos.getCounts
251
+ # flickr.photos.getNotInSet
252
+ # flickr.photos.getUntagged
253
+ # flickr.photosets.create
254
+ # flickr.photosets.orderSets
255
+ # flickr.test.login
256
+ # uploading
257
+ class User
258
+
259
+ attr_reader :client, :id, :name, :location, :photos_url, :url, :count, :firstdate, :firstdatetaken
260
+
261
+ # A Flickr::User can be instantiated in two ways. The old (deprecated)
262
+ # method is with an ordered series of values. The new method is with a
263
+ # params Hash, which is easier when a variable number of params are
264
+ # supplied, which is the case here, and also avoids having to constantly
265
+ # supply nil values for the email and password, which are now irrelevant
266
+ # as authentication is no longer done this way.
267
+ # An associated flickr client will also be generated if an api key is
268
+ # passed among the arguments or in the params hash. Alternatively, and
269
+ # most likely, an existing client object may be passed in the params hash
270
+ # (e.g. 'client' => some_existing_flickr_client_object), and this is
271
+ # what happends when users are initlialized as the result of a method
272
+ # called on the flickr client (e.g. flickr.users)
273
+ def initialize(id_or_params_hash=nil, username=nil, email=nil, password=nil, api_key=nil)
274
+ if id_or_params_hash.is_a?(Hash)
275
+ id_or_params_hash.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
276
+ else
277
+ @id = id_or_params_hash
278
+ @username = username
279
+ @email = email
280
+ @password = password
281
+ @api_key = api_key
282
+ end
283
+ @client ||= Flickr.new('api_key' => @api_key, 'shared_secret' => @shared_secret, 'auth_token' => @auth_token) if @api_key
284
+ @client.login(@email, @password) if @email and @password # this is now irrelevant as Flickr API no longer supports authentication this way
285
+ end
286
+
287
+ def username
288
+ @username.nil? ? getInfo.username : @username
289
+ end
290
+ def name
291
+ @name.nil? ? getInfo.name : @name
292
+ end
293
+ def location
294
+ @location.nil? ? getInfo.location : @location
295
+ end
296
+ def count
297
+ @count.nil? ? getInfo.count : @count
298
+ end
299
+ def firstdate
300
+ @firstdate.nil? ? getInfo.firstdate : @firstdate
301
+ end
302
+ def firstdatetaken
303
+ @firstdatetaken.nil? ? getInfo.firstdatetaken : @firstdatetaken
304
+ end
305
+
306
+ # Builds url for user's photos page as per
307
+ # http://www.flickr.com/services/api/misc.urls.html
308
+ def photos_url
309
+ "http://www.flickr.com/photos/#{id}/"
310
+ end
311
+
312
+ # Builds url for user's profile page as per
313
+ # http://www.flickr.com/services/api/misc.urls.html
314
+ def url
315
+ "http://www.flickr.com/people/#{id}/"
316
+ end
317
+
318
+ def pretty_url
319
+ @pretty_url ||= @client.urls_getUserProfile('user_id'=>@id)['user']['url']
320
+ end
321
+
322
+ # Implements flickr.people.getPublicGroups
323
+ def groups
324
+ collection = @client.people_getPublicGroups('user_id'=>@id)['groups']['group']
325
+ collection = [collection] if collection.is_a? Hash
326
+ collection.collect { |group| Group.new( "id" => group['nsid'],
327
+ "name" => group['name'],
328
+ "eighteenplus" => group['eighteenplus'],
329
+ "client" => @client) }
330
+ end
331
+
332
+ # Implements flickr.people.getPublicPhotos. Options hash allows you to add
333
+ # extra restrictions as per flickr.people.getPublicPhotos docs, e.g.
334
+ # user.photos('per_page' => '25', 'extras' => 'date_taken')
335
+ def photos(options={})
336
+ @client.photos_request('people.getPublicPhotos', {'user_id' => @id}.merge(options))
337
+ # what about non-public photos?
338
+ end
339
+
340
+ # Gets photos with a given tag
341
+ def tag(tag)
342
+ @client.photos('user_id'=>@id, 'tags'=>tag)
343
+ end
344
+
345
+ # Implements flickr.contacts.getPublicList and flickr.contacts.getList
346
+ def contacts
347
+ @client.contacts_getPublicList('user_id'=>@id)['contacts']['contact'].collect { |contact| User.new(contact['nsid'], contact['username'], nil, nil, @api_key) }
348
+ #or
349
+ end
350
+
351
+ # Implements flickr.favorites.getPublicList
352
+ def favorites
353
+ @client.photos_request('favorites.getPublicList', 'user_id' => @id)
354
+ end
355
+
356
+ # Implements flickr.photosets.getList
357
+ def photosets
358
+ @client.photosets_getList('user_id'=>@id)['photosets']['photoset'].collect { |photoset| Photoset.new(photoset['id'], @api_key) }
359
+ end
360
+
361
+ # Implements flickr.tags.getListUser
362
+ def tags
363
+ @client.tags_getListUser('user_id'=>@id)['who']['tags']['tag'].collect { |tag| tag }
364
+ end
365
+
366
+ # Implements flickr.tags.getListUserPopular
367
+ def popular_tags(count = 10)
368
+ @client.tags_getListUserPopular('user_id'=>@id, 'count'=> count)['who']['tags']['tag'].each { |tag_score| tag_score["tag"] = tag_score.delete("content") }
369
+ end
370
+
371
+ # Implements flickr.photos.getContactsPublicPhotos and flickr.photos.getContactsPhotos
372
+ def contactsPhotos
373
+ @client.photos_request('photos.getContactsPublicPhotos', 'user_id' => @id)
374
+ end
375
+
376
+ def to_s
377
+ @name
378
+ end
379
+
380
+ private
381
+
382
+ # Implements flickr.people.getInfo, flickr.urls.getUserPhotos, and flickr.urls.getUserProfile
383
+ def getInfo
384
+ info = @client.people_getInfo('user_id'=>@id)['person']
385
+ @username = info['username']
386
+ @name = info['realname']
387
+ @location = info['location']
388
+ @count = info['photos']['count']
389
+ @firstdate = info['photos']['firstdate']
390
+ @firstdatetaken = info['photos']['firstdatetaken']
391
+ self
392
+ end
393
+
394
+ end
395
+
396
+ class Photo
397
+
398
+ attr_reader :id, :client, :title
399
+
400
+ def initialize(id=nil, api_key=nil, extra_params={})
401
+ @id = id
402
+ @api_key = api_key
403
+ extra_params.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
404
+ @client = Flickr.new @api_key
405
+ end
406
+
407
+ # Allows access to all photos instance variables through hash like
408
+ # interface, e.g. photo["datetaken"] returns @datetaken instance
409
+ # variable. Useful for accessing any weird and wonderful parameter
410
+ # that may have been returned by Flickr when finding the photo,
411
+ # e.g. those returned by the extras argument in
412
+ # flickr.people.getPublicPhotos
413
+ def [](param_name)
414
+ instance_variable_get("@#{param_name}")
415
+ end
416
+
417
+ def title
418
+ @title.nil? ? getInfo("title") : @title
419
+ end
420
+
421
+ # Returns the owner of the photo as a Flickr::User. If we have no info
422
+ # about the owner, we make an API call to get it. If we already have
423
+ # the owner's id, create a user based on that. Either way, we cache the
424
+ # result so we don't need to check again
425
+ def owner
426
+ case @owner
427
+ when Flickr::User
428
+ @owner
429
+ when String
430
+ @owner = Flickr::User.new(@owner, nil, nil, nil, @api_key)
431
+ else
432
+ getInfo("owner")
433
+ end
434
+ end
435
+
436
+ def server
437
+ @server.nil? ? getInfo("server") : @server
438
+ end
439
+
440
+ def isfavorite
441
+ @isfavorite.nil? ? getInfo("isfavorite") : @isfavorite
442
+ end
443
+
444
+ def license
445
+ @license.nil? ? getInfo("license") : @license
446
+ end
447
+
448
+ def rotation
449
+ @rotation.nil? ? getInfo("rotation") : @rotation
450
+ end
451
+
452
+ def description
453
+ @description || getInfo("description")
454
+ end
455
+
456
+ def notes
457
+ @notes.nil? ? getInfo("notes") : @notes
458
+ end
459
+
460
+ # Returns the URL for the photo size page
461
+ # defaults to 'Medium'
462
+ # other valid sizes are in the VALID_SIZES hash
463
+ def size_url(size='Medium')
464
+ uri_for_photo_from_self(size) || sizes(size)['url']
465
+ end
466
+
467
+ # converts string or symbol size to a capitalized string
468
+ def normalize_size(size)
469
+ size ? size.to_s.capitalize : size
470
+ end
471
+
472
+ # the URL for the main photo page
473
+ # if getInfo has already been called, this will return the pretty url
474
+ #
475
+ # for historical reasons, an optional size can be given
476
+ # 'Medium' returns the regular url; any other size returns a size page
477
+ # use size_url instead
478
+ def url(size = nil)
479
+ if normalize_size(size) != 'Medium'
480
+ size_url(size)
481
+ else
482
+ @url || uri_for_photo_from_self
483
+ end
484
+ end
485
+
486
+ # the 'pretty' url for a photo
487
+ # (if the user has set up a custom name)
488
+ # eg, http://flickr.com/photos/granth/2584402507/ instead of
489
+ # http://flickr.com/photos/23386158@N00/2584402507/
490
+ def pretty_url
491
+ @url || getInfo("pretty_url")
492
+ end
493
+
494
+ # Returns the URL for the image (default or any specified size)
495
+ def source(size='Medium')
496
+ image_source_uri_from_self(size) || sizes(size)['source']
497
+ end
498
+
499
+ # Returns the photo file data itself, in any specified size. Example: File.open(photo.title, 'w') { |f| f.puts photo.file }
500
+ def file(size='Medium')
501
+ Net::HTTP.get_response(URI.parse(source(size))).body
502
+ end
503
+
504
+ # Unique filename for the image, based on the Flickr NSID
505
+ def filename
506
+ "#{@id}.jpg"
507
+ end
508
+
509
+ # Implements flickr.photos.getContext
510
+ def context
511
+ context = @client.photos_getContext('photo_id'=>@id)
512
+ @previousPhoto = Photo.new(context['prevphoto'].delete('id'), @api_key, context['prevphoto']) if context['prevphoto']['id']!='0'
513
+ @nextPhoto = Photo.new(context['nextphoto'].delete('id'), @api_key, context['nextphoto']) if context['nextphoto']['id']!='0'
514
+ return [@previousPhoto, @nextPhoto]
515
+ end
516
+
517
+ # Implements flickr.photos.getExif
518
+ def exif
519
+ @client.photos_getExif('photo_id'=>@id)['photo']
520
+ end
521
+
522
+ # Implements flickr.photos.getPerms
523
+ def permissions
524
+ @client.photos_getPerms('photo_id'=>@id)['perms']
525
+ end
526
+
527
+ # Implements flickr.photos.getSizes
528
+ def sizes(size=nil)
529
+ size = normalize_size(size)
530
+ sizes = @client.photos_getSizes('photo_id'=>@id)['sizes']['size']
531
+ sizes = sizes.find{|asize| asize['label']==size} if size
532
+ return sizes
533
+ end
534
+
535
+ # flickr.tags.getListPhoto
536
+ def tags
537
+ @client.tags_getListPhoto('photo_id'=>@id)['photo']['tags']
538
+ end
539
+
540
+ # Implements flickr.photos.notes.add
541
+ def add_note(note)
542
+ end
543
+
544
+ # Implements flickr.photos.setDates
545
+ def dates=(dates)
546
+ end
547
+
548
+ # Implements flickr.photos.setPerms
549
+ def perms=(perms)
550
+ end
551
+
552
+ # Implements flickr.photos.setTags
553
+ def tags=(tags)
554
+ end
555
+
556
+ # Implements flickr.photos.setMeta
557
+ def title=(title)
558
+ end
559
+ def description=(title)
560
+ end
561
+
562
+ # Implements flickr.photos.addTags
563
+ def add_tag(tag)
564
+ end
565
+
566
+ # Implements flickr.photos.removeTag
567
+ def remove_tag(tag)
568
+ end
569
+
570
+ # Implements flickr.photos.transform.rotate
571
+ def rotate
572
+ end
573
+
574
+ # Implements flickr.blogs.postPhoto
575
+ def postToBlog(blog_id, title='', description='')
576
+ @client.blogs_postPhoto('photo_id'=>@id, 'title'=>title, 'description'=>description)
577
+ end
578
+
579
+ # Implements flickr.photos.notes.delete
580
+ def deleteNote(note_id)
581
+ end
582
+
583
+ # Implements flickr.photos.notes.edit
584
+ def editNote(note_id)
585
+ end
586
+
587
+ # Converts the Photo to a string by returning its title
588
+ def to_s
589
+ title
590
+ end
591
+
592
+ private
593
+
594
+ # Implements flickr.photos.getInfo
595
+ def getInfo(attrib="")
596
+ return instance_variable_get("@#{attrib}") if @got_info
597
+ info = @client.photos_getInfo('photo_id'=>@id)['photo']
598
+ @got_info = true
599
+ info.each { |k,v| instance_variable_set("@#{k}", v)}
600
+ @owner = User.new(info['owner']['nsid'], info['owner']['username'], nil, nil, @api_key)
601
+ @tags = info['tags']['tag']
602
+ @notes = info['notes']['note']#.collect { |note| Note.new(note.id) }
603
+ @url = info['urls']['url']['content'] # assumes only one url
604
+ instance_variable_get("@#{attrib}")
605
+ end
606
+
607
+ # Builds source uri of image from params (often returned from other
608
+ # methods, e.g. User#photos). As specified at:
609
+ # http://www.flickr.com/services/api/misc.urls.html. If size is given
610
+ # should be one the keys in the VALID_SIZES hash, i.e.
611
+ # "Square", "Thumbnail", "Medium", "Large", "Original", "Small" (These
612
+ # are the values returned by flickr.photos.getSizes).
613
+ # If no size is given the uri for "Medium"-size image, i.e. with width
614
+ # of 500 is returned
615
+ # TODO: Handle "Original" size
616
+ def image_source_uri_from_self(size=nil)
617
+ return unless @farm&&@server&&@id&&@secret
618
+ s_size = VALID_SIZES[normalize_size(size)] # get the short letters array corresponding to the size
619
+ s_size = s_size&&s_size[0] # the first element of this array is used to build the source uri
620
+ if s_size.nil?
621
+ "http://farm#{@farm}.static.flickr.com/#{@server}/#{@id}_#{@secret}.jpg"
622
+ else
623
+ "http://farm#{@farm}.static.flickr.com/#{@server}/#{@id}_#{@secret}_#{s_size}.jpg"
624
+ end
625
+ end
626
+
627
+ # Builds uri of Flickr page for photo. By default returns the main
628
+ # page for the photo, but if passed a size will return the simplified
629
+ # flickr page featuring the given size of the photo
630
+ # TODO: Handle "Original" size
631
+ def uri_for_photo_from_self(size=nil)
632
+ return unless @owner&&@id
633
+ size = normalize_size(size)
634
+ s_size = VALID_SIZES[size] # get the short letters array corresponding to the size
635
+ 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
636
+ "http://www.flickr.com/photos/#{owner.id}/#{@id}" + (s_size ? "/sizes/#{s_size}/" : "")
637
+ end
638
+ end
639
+
640
+ # Todo:
641
+ # flickr.groups.pools.add
642
+ # flickr.groups.pools.getContext
643
+ # flickr.groups.pools.getGroups
644
+ # flickr.groups.pools.getPhotos
645
+ # flickr.groups.pools.remove
646
+ class Group
647
+ attr_reader :id, :client, :description, :name, :eighteenplus, :members, :online, :privacy, :url#, :chatid, :chatcount
648
+
649
+ def initialize(id_or_params_hash=nil, api_key=nil)
650
+ if id_or_params_hash.is_a?(Hash)
651
+ id_or_params_hash.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
652
+ else
653
+ @id = id_or_params_hash
654
+ @api_key = api_key
655
+ @client = Flickr.new @api_key
656
+ end
657
+ end
658
+
659
+ # Implements flickr.groups.getInfo and flickr.urls.getGroup
660
+ # private, once we can call it as needed
661
+ def getInfo
662
+ info = @client.groups_getInfo('group_id'=>@id)['group']
663
+ @name = info['name']
664
+ @members = info['members']
665
+ @online = info['online']
666
+ @privacy = info['privacy']
667
+ # @chatid = info['chatid']
668
+ # @chatcount = info['chatcount']
669
+ @url = @client.urls_getGroup('group_id'=>@id)['group']['url']
670
+ self
671
+ end
672
+
673
+ end
674
+
675
+ # Todo:
676
+ # flickr.photosets.delete
677
+ # flickr.photosets.editMeta
678
+ # flickr.photosets.editPhotos
679
+ # flickr.photosets.getContext
680
+ # flickr.photosets.getInfo
681
+ # flickr.photosets.getPhotos
682
+ class Photoset
683
+
684
+ attr_reader :id, :client, :owner, :primary, :photos, :title, :description, :url
685
+
686
+ def initialize(id=nil, api_key=nil)
687
+ @id = id
688
+ @api_key = api_key
689
+ @client = Flickr.new @api_key
690
+ end
691
+
692
+ # Implements flickr.photosets.getInfo
693
+ # private, once we can call it as needed
694
+ def getInfo
695
+ info = @client.photosets_getInfo('photoset_id'=>@id)['photoset']
696
+ @owner = User.new(info['owner'], nil, nil, nil, @api_key)
697
+ @primary = info['primary']
698
+ @photos = info['photos']
699
+ @title = info['title']
700
+ @description = info['description']
701
+ @url = "http://www.flickr.com/photos/#{@owner.getInfo.username}/sets/#{@id}/"
702
+ self
703
+ end
704
+
705
+ def getPhotos
706
+ photosetPhotos = @client.photos_request('photosets.getPhotos', {'photoset_id' => @id})
707
+ end
708
+
709
+ end
710
+
711
+ end