reagent-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 +43 -0
  2. data/LICENSE +20 -0
  3. data/README.txt +73 -0
  4. data/TODO +6 -0
  5. data/lib/flickr.rb +675 -0
  6. data/test/test_flickr.rb +892 -0
  7. metadata +66 -0
@@ -0,0 +1,43 @@
1
+ == 1.0.6 2008-07-30
2
+ * Bugfixes:
3
+ * fixed Flickr#photos when used for searching
4
+ * fixed Flickr::Photo#url to not use usernames (if the user changes their username, the url will wrong)
5
+ * fixed Flickr#related_tags
6
+ * Flickr::Photo#to_s was making unnecessary API calls
7
+ * fixed 'test' Rake task
8
+ * fixed 'gem' Rake task
9
+ * Minor enhancements:
10
+ * added Flickr#search as an alias for Flickr#photos in its search mode
11
+ * added Flickr#recent as an alias for Flickr#photos for recent photos
12
+ * added Flickr::Photo#pretty_url for the URL that includes a username if present
13
+ * added Flickr::Photo#size_url which is like url except 'Medium' works the same as other sizes
14
+ * allow lowercase ('medium') and symbol (:medium) forms for sizes
15
+ * internal code cleanup
16
+
17
+ == 1.0.5 2008-05-12
18
+
19
+ * 1 major change:
20
+ * 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)
21
+ * Minor enhancements:
22
+ * Tweaked internals so new client instance isn't created each time new object (e.g. photo, user) is created
23
+ * Improved test coverage
24
+
25
+ == 1.0.4 2008-05-11
26
+
27
+ * 1 major enhancement:
28
+ * Added authentication facility as per current Flickr API
29
+ * 3 minor enhancements:
30
+ * Improved test suite
31
+ * Improved API for creating Flickr objects (old one still works)
32
+ * Protected methods that are only used internally (e.g. request, request_url)
33
+
34
+ == 1.0.3 2008-04-18
35
+
36
+ * Various bugfixes:
37
+ * User instantiation was broken (wrong number of params passed)
38
+ * Instantiating photos failed in several places if single photo returned
39
+ * Photosets#getInfo would always fail as the parameter name should be photoset_id, not photosets_id
40
+ * Removed call to flickr.people.getOnlineList in Flickr#users as that call is no longer in the Flickr API
41
+ * Performance improvements:
42
+ * Url and image source uri now generated without a call to the Flickr API, as per the Flickr API docs
43
+
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,675 @@
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 list of Photo objects
196
+ def photos_request(method, params={})
197
+ photos = request(method, params)
198
+ collection = photos['photos']['photo'] || []
199
+ collection = [collection] if collection.is_a? Hash
200
+ collection.collect { |photo| Photo.new(photo.delete('id'), @api_key, photo) }
201
+ end
202
+
203
+ # Builds url for Flickr API REST request from given the flickr method name
204
+ # (exclusing the 'flickr.' that begins each method call) and params (where
205
+ # applicable) which should be supplied as a Hash (e.g 'user_id' => "foo123")
206
+ def request_url(method, params={})
207
+ method = 'flickr.' + method
208
+ url = "#{@host}#{@api}/?api_key=#{@api_key}&method=#{method}"
209
+ params.merge!('api_key' => @api_key, 'method' => method, 'auth_token' => @auth_token)
210
+ signature = signature_from(params)
211
+
212
+ url = "#{@host}#{@api}/?" + params.merge('api_sig' => signature).collect { |k,v| "#{k}=" + CGI::escape(v.to_s) unless v.nil? }.compact.join("&")
213
+ end
214
+
215
+ def signature_from(params={})
216
+ return unless @shared_secret # don't both getting signature if no shared_secret
217
+ 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
218
+ return Digest::MD5.hexdigest("#{@shared_secret}#{request_str}")
219
+ end
220
+
221
+ # Todo:
222
+ # logged_in?
223
+ # if logged in:
224
+ # flickr.blogs.getList
225
+ # flickr.favorites.add
226
+ # flickr.favorites.remove
227
+ # flickr.groups.browse
228
+ # flickr.photos.getCounts
229
+ # flickr.photos.getNotInSet
230
+ # flickr.photos.getUntagged
231
+ # flickr.photosets.create
232
+ # flickr.photosets.orderSets
233
+ # flickr.tags.getListUserPopular
234
+ # flickr.test.login
235
+ # uploading
236
+ class User
237
+
238
+ attr_reader :client, :id, :name, :location, :photos_url, :url, :count, :firstdate, :firstdatetaken
239
+
240
+ # A Flickr::User can be instantiated in two ways. The old (deprecated)
241
+ # method is with an ordered series of values. The new method is with a
242
+ # params Hash, which is easier when a variable number of params are
243
+ # supplied, which is the case here, and also avoids having to constantly
244
+ # supply nil values for the email and password, which are now irrelevant
245
+ # as authentication is no longer done this way.
246
+ # An associated flickr client will also be generated if an api key is
247
+ # passed among the arguments or in the params hash. Alternatively, and
248
+ # most likely, an existing client object may be passed in the params hash
249
+ # (e.g. 'client' => some_existing_flickr_client_object), and this is
250
+ # what happends when users are initlialized as the result of a method
251
+ # called on the flickr client (e.g. flickr.users)
252
+ def initialize(id_or_params_hash=nil, username=nil, email=nil, password=nil, api_key=nil)
253
+ if id_or_params_hash.is_a?(Hash)
254
+ id_or_params_hash.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
255
+ else
256
+ @id = id_or_params_hash
257
+ @username = username
258
+ @email = email
259
+ @password = password
260
+ @api_key = api_key
261
+ end
262
+ @client ||= Flickr.new('api_key' => @api_key, 'shared_secret' => @shared_secret, 'auth_token' => @auth_token) if @api_key
263
+ @client.login(@email, @password) if @email and @password # this is now irrelevant as Flickr API no longer supports authentication this way
264
+ end
265
+
266
+ def username
267
+ @username.nil? ? getInfo.username : @username
268
+ end
269
+ def name
270
+ @name.nil? ? getInfo.name : @name
271
+ end
272
+ def location
273
+ @location.nil? ? getInfo.location : @location
274
+ end
275
+ def count
276
+ @count.nil? ? getInfo.count : @count
277
+ end
278
+ def firstdate
279
+ @firstdate.nil? ? getInfo.firstdate : @firstdate
280
+ end
281
+ def firstdatetaken
282
+ @firstdatetaken.nil? ? getInfo.firstdatetaken : @firstdatetaken
283
+ end
284
+ def photos_url
285
+ @photos_url.nil? ? getInfo.photos_url : @photos_url
286
+ end
287
+ def url
288
+ @url.nil? ? getInfo.url : @url
289
+ end
290
+
291
+ # Implements flickr.people.getPublicGroups
292
+ def groups
293
+ collection = @client.people_getPublicGroups('user_id'=>@id)['groups']['group']
294
+ collection = [collection] if collection.is_a? Hash
295
+ collection.collect { |group| Group.new( "id" => group['nsid'],
296
+ "name" => group['name'],
297
+ "eighteenplus" => group['eighteenplus'],
298
+ "client" => @client) }
299
+ end
300
+
301
+ # Implements flickr.people.getPublicPhotos. Options hash allows you to add
302
+ # extra restrictions as per flickr.people.getPublicPhotos docs, e.g.
303
+ # user.photos('per_page' => '25', 'extras' => 'date_taken')
304
+ def photos(options={})
305
+ @client.photos_request('people.getPublicPhotos', {'user_id' => @id}.merge(options))
306
+ # what about non-public photos?
307
+ end
308
+
309
+ # Gets photos with a given tag
310
+ def tag(tag)
311
+ @client.photos('user_id'=>@id, 'tags'=>tag)
312
+ end
313
+
314
+ # Implements flickr.contacts.getPublicList and flickr.contacts.getList
315
+ def contacts
316
+ @client.contacts_getPublicList('user_id'=>@id)['contacts']['contact'].collect { |contact| User.new(contact['nsid'], contact['username'], nil, nil, @api_key) }
317
+ #or
318
+ end
319
+
320
+ # Implements flickr.favorites.getPublicList
321
+ def favorites
322
+ @client.photos_request('favorites.getPublicList', 'user_id' => @id)
323
+ end
324
+
325
+ # Implements flickr.photosets.getList
326
+ def photosets
327
+ @client.photosets_getList('user_id'=>@id)['photosets']['photoset'].collect { |photoset| Photoset.new(photoset['id'], @api_key) }
328
+ end
329
+
330
+ # Implements flickr.tags.getListUser
331
+ def tags
332
+ @client.tags_getListUser('user_id'=>@id)['who']['tags']['tag'].collect { |tag| tag }
333
+ end
334
+
335
+ # Implements flickr.photos.getContactsPublicPhotos and flickr.photos.getContactsPhotos
336
+ def contactsPhotos
337
+ @client.photos_request('photos.getContactsPublicPhotos', 'user_id' => @id)
338
+ end
339
+
340
+ def to_s
341
+ @name
342
+ end
343
+
344
+ private
345
+
346
+ # Implements flickr.people.getInfo, flickr.urls.getUserPhotos, and flickr.urls.getUserProfile
347
+ def getInfo
348
+ info = @client.people_getInfo('user_id'=>@id)['person']
349
+ @username = info['username']
350
+ @name = info['realname']
351
+ @location = info['location']
352
+ @count = info['photos']['count']
353
+ @firstdate = info['photos']['firstdate']
354
+ @firstdatetaken = info['photos']['firstdatetaken']
355
+ @photos_url = @client.urls_getUserPhotos('user_id'=>@id)['user']['url']
356
+ @url = @client.urls_getUserProfile('user_id'=>@id)['user']['url']
357
+ self
358
+ end
359
+
360
+ end
361
+
362
+ class Photo
363
+
364
+ attr_reader :id, :client, :title
365
+
366
+ def initialize(id=nil, api_key=nil, extra_params={})
367
+ @id = id
368
+ @api_key = api_key
369
+ extra_params.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
370
+ @client = Flickr.new @api_key
371
+ end
372
+
373
+ # Allows access to all photos instance variables through hash like
374
+ # interface, e.g. photo["datetaken"] returns @datetaken instance
375
+ # variable. Useful for accessing any weird and wonderful parameter
376
+ # that may have been returned by Flickr when finding the photo,
377
+ # e.g. those returned by the extras argument in
378
+ # flickr.people.getPublicPhotos
379
+ def [](param_name)
380
+ instance_variable_get("@#{param_name}")
381
+ end
382
+
383
+ def title
384
+ @title.nil? ? getInfo.title : @title
385
+ end
386
+
387
+ # Returns the owner of the photo as a Flickr::User. If we have no info
388
+ # about the owner, we make an API call to get it. If we already have
389
+ # the owner's id, create a user based on that. Either way, we cache the
390
+ # result so we don't need to check again
391
+ def owner
392
+ case @owner
393
+ when Flickr::User
394
+ @owner
395
+ when String
396
+ @owner = Flickr::User.new(@owner, nil, nil, nil, @api_key)
397
+ else
398
+ getInfo.owner
399
+ end
400
+ end
401
+
402
+ def server
403
+ @server.nil? ? getInfo.server : @server
404
+ end
405
+
406
+ def isfavorite
407
+ @isfavorite.nil? ? getInfo.isfavorite : @isfavorite
408
+ end
409
+
410
+ def license
411
+ @license.nil? ? getInfo.license : @license
412
+ end
413
+
414
+ def rotation
415
+ @rotation.nil? ? getInfo.rotation : @rotation
416
+ end
417
+
418
+ def description
419
+ @description.nil? ? getInfo.description : @description
420
+ end
421
+
422
+ def notes
423
+ @notes.nil? ? getInfo.notes : @notes
424
+ end
425
+
426
+ # Returns the URL for the photo size page
427
+ # defaults to 'Medium'
428
+ # other valid sizes are in the VALID_SIZES hash
429
+ def size_url(size='Medium')
430
+ uri_for_photo_from_self(size) || sizes(size)['url']
431
+ end
432
+
433
+ # converts string or symbol size to a capitalized string
434
+ def normalize_size(size)
435
+ size ? size.to_s.capitalize : size
436
+ end
437
+
438
+ # the URL for the main photo page
439
+ # if getInfo has already been called, this will return the pretty url
440
+ #
441
+ # for historical reasons, an optional size can be given
442
+ # 'Medium' returns the regular url; any other size returns a size page
443
+ # use size_url instead
444
+ def url(size = nil)
445
+ if normalize_size(size) != 'Medium'
446
+ size_url(size)
447
+ else
448
+ @url || uri_for_photo_from_self
449
+ end
450
+ end
451
+
452
+ # the 'pretty' url for a photo
453
+ # (if the user has set up a custom name)
454
+ # eg, http://flickr.com/photos/granth/2584402507/ instead of
455
+ # http://flickr.com/photos/23386158@N00/2584402507/
456
+ def pretty_url
457
+ @url || getInfo.pretty_url
458
+ end
459
+
460
+ # Returns the URL for the image (default or any specified size)
461
+ def source(size='Medium')
462
+ image_source_uri_from_self(size) || sizes(size)['source']
463
+ end
464
+
465
+ # Returns the photo file data itself, in any specified size. Example: File.open(photo.title, 'w') { |f| f.puts photo.file }
466
+ def file(size='Medium')
467
+ Net::HTTP.get_response(URI.parse(source(size))).body
468
+ end
469
+
470
+ # Unique filename for the image, based on the Flickr NSID
471
+ def filename
472
+ "#{@id}.jpg"
473
+ end
474
+
475
+ # Implements flickr.photos.getContext
476
+ def context
477
+ context = @client.photos_getContext('photo_id'=>@id)
478
+ @previousPhoto = Photo.new(context['prevphoto'].delete('id'), @api_key, context['prevphoto']) if context['prevphoto']['id']!='0'
479
+ @nextPhoto = Photo.new(context['nextphoto'].delete('id'), @api_key, context['nextphoto']) if context['nextphoto']['id']!='0'
480
+ return [@previousPhoto, @nextPhoto]
481
+ end
482
+
483
+ # Implements flickr.photos.getExif
484
+ def exif
485
+ @client.photos_getExif('photo_id'=>@id)['photo']
486
+ end
487
+
488
+ # Implements flickr.photos.getPerms
489
+ def permissions
490
+ @client.photos_getPerms('photo_id'=>@id)['perms']
491
+ end
492
+
493
+ # Implements flickr.photos.getSizes
494
+ def sizes(size=nil)
495
+ size = normalize_size(size)
496
+ sizes = @client.photos_getSizes('photo_id'=>@id)['sizes']['size']
497
+ sizes = sizes.find{|asize| asize['label']==size} if size
498
+ return sizes
499
+ end
500
+
501
+ # flickr.tags.getListPhoto
502
+ def tags
503
+ @client.tags_getListPhoto('photo_id'=>@id)['photo']['tags']
504
+ end
505
+
506
+ # Implements flickr.photos.notes.add
507
+ def add_note(note)
508
+ end
509
+
510
+ # Implements flickr.photos.setDates
511
+ def dates=(dates)
512
+ end
513
+
514
+ # Implements flickr.photos.setPerms
515
+ def perms=(perms)
516
+ end
517
+
518
+ # Implements flickr.photos.setTags
519
+ def tags=(tags)
520
+ end
521
+
522
+ # Implements flickr.photos.setMeta
523
+ def title=(title)
524
+ end
525
+ def description=(title)
526
+ end
527
+
528
+ # Implements flickr.photos.addTags
529
+ def add_tag(tag)
530
+ end
531
+
532
+ # Implements flickr.photos.removeTag
533
+ def remove_tag(tag)
534
+ end
535
+
536
+ # Implements flickr.photos.transform.rotate
537
+ def rotate
538
+ end
539
+
540
+ # Implements flickr.blogs.postPhoto
541
+ def postToBlog(blog_id, title='', description='')
542
+ @client.blogs_postPhoto('photo_id'=>@id, 'title'=>title, 'description'=>description)
543
+ end
544
+
545
+ # Implements flickr.photos.notes.delete
546
+ def deleteNote(note_id)
547
+ end
548
+
549
+ # Implements flickr.photos.notes.edit
550
+ def editNote(note_id)
551
+ end
552
+
553
+ # Converts the Photo to a string by returning its title
554
+ def to_s
555
+ title
556
+ end
557
+
558
+ private
559
+
560
+ # Implements flickr.photos.getInfo
561
+ def getInfo
562
+ info = @client.photos_getInfo('photo_id'=>@id)['photo']
563
+ @title = info['title']
564
+ @owner = User.new(info['owner']['nsid'], info['owner']['username'], nil, nil, @api_key)
565
+ @server = info['server']
566
+ @isfavorite = info['isfavorite']
567
+ @license = info['license']
568
+ @rotation = info['rotation']
569
+ @description = info['description']
570
+ @notes = info['notes']['note']#.collect { |note| Note.new(note.id) }
571
+ @url = info['urls']['url']['content'] # assumes only one url
572
+ self
573
+ end
574
+
575
+ # Builds source uri of image from params (often returned from other
576
+ # methods, e.g. User#photos). As specified at:
577
+ # http://www.flickr.com/services/api/misc.urls.html. If size is given
578
+ # should be one the keys in the VALID_SIZES hash, i.e.
579
+ # "Square", "Thumbnail", "Medium", "Large", "Original", "Small" (These
580
+ # are the values returned by flickr.photos.getSizes).
581
+ # If no size is given the uri for "Medium"-size image, i.e. with width
582
+ # of 500 is returned
583
+ # TODO: Handle "Original" size
584
+ def image_source_uri_from_self(size=nil)
585
+ return unless @farm&&@server&&@id&&@secret
586
+ s_size = VALID_SIZES[normalize_size(size)] # get the short letters array corresponding to the size
587
+ s_size = s_size&&s_size[0] # the first element of this array is used to build the source uri
588
+ if s_size.nil?
589
+ "http://farm#{@farm}.static.flickr.com/#{@server}/#{@id}_#{@secret}.jpg"
590
+ else
591
+ "http://farm#{@farm}.static.flickr.com/#{@server}/#{@id}_#{@secret}_#{s_size}.jpg"
592
+ end
593
+ end
594
+
595
+ # Builds uri of Flickr page for photo. By default returns the main
596
+ # page for the photo, but if passed a size will return the simplified
597
+ # flickr page featuring the given size of the photo
598
+ # TODO: Handle "Original" size
599
+ def uri_for_photo_from_self(size=nil)
600
+ return unless @owner&&@id
601
+ size = normalize_size(size)
602
+ s_size = VALID_SIZES[size] # get the short letters array corresponding to the size
603
+ 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
604
+ "http://www.flickr.com/photos/#{owner.id}/#{@id}" + (s_size ? "/sizes/#{s_size}/" : "")
605
+ end
606
+ end
607
+
608
+ # Todo:
609
+ # flickr.groups.pools.add
610
+ # flickr.groups.pools.getContext
611
+ # flickr.groups.pools.getGroups
612
+ # flickr.groups.pools.getPhotos
613
+ # flickr.groups.pools.remove
614
+ class Group
615
+ attr_reader :id, :client, :description, :name, :eighteenplus, :members, :online, :privacy, :url#, :chatid, :chatcount
616
+
617
+ def initialize(id_or_params_hash=nil, api_key=nil)
618
+ if id_or_params_hash.is_a?(Hash)
619
+ id_or_params_hash.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
620
+ else
621
+ @id = id_or_params_hash
622
+ @api_key = api_key
623
+ @client = Flickr.new @api_key
624
+ end
625
+ end
626
+
627
+ # Implements flickr.groups.getInfo and flickr.urls.getGroup
628
+ # private, once we can call it as needed
629
+ def getInfo
630
+ info = @client.groups_getInfo('group_id'=>@id)['group']
631
+ @name = info['name']
632
+ @members = info['members']
633
+ @online = info['online']
634
+ @privacy = info['privacy']
635
+ # @chatid = info['chatid']
636
+ # @chatcount = info['chatcount']
637
+ @url = @client.urls_getGroup('group_id'=>@id)['group']['url']
638
+ self
639
+ end
640
+
641
+ end
642
+
643
+ # Todo:
644
+ # flickr.photosets.delete
645
+ # flickr.photosets.editMeta
646
+ # flickr.photosets.editPhotos
647
+ # flickr.photosets.getContext
648
+ # flickr.photosets.getInfo
649
+ # flickr.photosets.getPhotos
650
+ class Photoset
651
+
652
+ attr_reader :id, :client, :owner, :primary, :photos, :title, :description, :url
653
+
654
+ def initialize(id=nil, api_key=nil)
655
+ @id = id
656
+ @api_key = api_key
657
+ @client = Flickr.new @api_key
658
+ end
659
+
660
+ # Implements flickr.photosets.getInfo
661
+ # private, once we can call it as needed
662
+ def getInfo
663
+ info = @client.photosets_getInfo('photoset_id'=>@id)['photoset']
664
+ @owner = User.new(info['owner'], nil, nil, nil, @api_key)
665
+ @primary = info['primary']
666
+ @photos = info['photos']
667
+ @title = info['title']
668
+ @description = info['description']
669
+ @url = "http://www.flickr.com/photos/#{@owner.getInfo.username}/sets/#{@id}/"
670
+ self
671
+ end
672
+
673
+ end
674
+
675
+ end