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