moonpxi-flickr 1.0.9

Sign up to get free protection for your applications and to get access to all the features.
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 +67 -0
@@ -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.
@@ -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
@@ -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://api.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