flickr.rb 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,70 @@
1
+ 1.1.0 2010-05-16
2
+ * Major improvement - now gem is very easy to use as a rails plugin with external configuration for api connection. - /config/flickr.api.yml
3
+
4
+ == 1.0.9 2010-05-16
5
+ * Few enhancements:
6
+ * New flickr api domain support: http://api.flickr.com
7
+ * depricated email/password params removed form Flickr#initialize and only hash method supported
8
+ * New rdoc documentation.
9
+ * tests fixed
10
+ * init.rb - file ready for using library as a rails 3.x plugin.
11
+
12
+ == 1.0.8 2009-01-25
13
+ * 2 minor enhancements
14
+ * Refactored initialization of PhotoCollection so can be instantiated from response returned by photosets.getPhotos
15
+ * Added photosets.getPhotos. Returns PhotoCollection
16
+
17
+ == 1.0.7 2008-11-26
18
+ * 1 major enhancement
19
+ * 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
20
+ * Minor enhancements:
21
+ * Refactored parsing of response when getting info about photo (public API is not changed).
22
+ * All returned info is now parsed and stored (previously only some attributes were)
23
+ * A record is kept of whether info has been previously fetched from Flickr (avoids unnecessary extra calls)
24
+ * Improved User#getInfo. No longer makes API call for pretty_url (which was in addition to main getInfo call) or photos_url
25
+ * Bugfixes
26
+ * Fixed Photo#source not to make unnecessary API call
27
+ * Fixed User#url not to make unnecessary API call
28
+
29
+ == 1.0.6 2008-07-30
30
+ * Bugfixes:
31
+ * fixed Flickr#photos when used for searching
32
+ * fixed Flickr::Photo#url to not use usernames (if the user changes their username, the url will wrong)
33
+ * fixed Flickr#related_tags
34
+ * Flickr::Photo#to_s was making unnecessary API calls
35
+ * fixed 'test' Rake task
36
+ * fixed 'gem' Rake task
37
+ * Minor enhancements:
38
+ * added Flickr#search as an alias for Flickr#photos in its search mode
39
+ * added Flickr#recent as an alias for Flickr#photos for recent photos
40
+ * added Flickr::Photo#pretty_url for the URL that includes a username if present
41
+ * added Flickr::Photo#size_url which is like url except 'Medium' works the same as other sizes
42
+ * allow lowercase ('medium') and symbol (:medium) forms for sizes
43
+ * internal code cleanup
44
+
45
+ == 1.0.5 2008-05-12
46
+
47
+ * 1 major change:
48
+ * 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)
49
+ * Minor enhancements:
50
+ * Tweaked internals so new client instance isn't created each time new object (e.g. photo, user) is created
51
+ * Improved test coverage
52
+
53
+ == 1.0.4 2008-05-11
54
+
55
+ * 1 major enhancement:
56
+ * Added authentication facility as per current Flickr API
57
+ * 3 minor enhancements:
58
+ * Improved test suite
59
+ * Improved API for creating Flickr objects (old one still works)
60
+ * Protected methods that are only used internally (e.g. request, request_url)
61
+
62
+ == 1.0.3 2008-04-18
63
+
64
+ * Various bugfixes:
65
+ * User instantiation was broken (wrong number of params passed)
66
+ * Instantiating photos failed in several places if single photo returned
67
+ * Photosets#getInfo would always fail as the parameter name should be photoset_id, not photosets_id
68
+ * Removed call to flickr.people.getOnlineList in Flickr#users as that call is no longer in the Flickr API
69
+ * Performance improvements:
70
+ * 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,10 @@
1
+ History.txt
2
+ index.html
3
+ init.rb
4
+ lib/flickr.rb
5
+ LICENSE
6
+ Manifest.txt
7
+ Rakefile
8
+ README.txt
9
+ test/flickr_test.rb
10
+ TODO
@@ -0,0 +1,90 @@
1
+ = flickr
2
+
3
+ http://github.com/RaVbaker/flickr
4
+
5
+ == DESCRIPTION:
6
+
7
+ An insanely easy interface to the Flickr photo-sharing service. By Scott Raymond. (& updated May 2008 by Chris Taggart, http://pushrod.wordpress.com & updated May 2010 by Rafal Piekarski)
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://api.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
+ == CONFIGURING:
52
+
53
+ If you want to use this gem/plugin with Rails you can create configuration file in /config directory with specified api connection settings. For example:
54
+
55
+ development:
56
+ api_key: SomeLongApiKey
57
+ shared_secret: secret!
58
+ auth_token: authSecretToken
59
+
60
+ beta:
61
+ api_key: SomeLongApiKeyBeta
62
+ shared_secret: secretBeta!
63
+ auth_token: authSecretTokenBeta
64
+
65
+
66
+
67
+ == LICENSE:
68
+
69
+ (The MIT License)
70
+
71
+ Copyright (c) 2005-2010 Scott Raymond, Patrick Plattes, Chris Taggart, Rafal Piekarski
72
+
73
+ Permission is hereby granted, free of charge, to any person obtaining
74
+ a copy of this software and associated documentation files (the
75
+ 'Software'), to deal in the Software without restriction, including
76
+ without limitation the rights to use, copy, modify, merge, publish,
77
+ distribute, sublicense, and/or sell copies of the Software, and to
78
+ permit persons to whom the Software is furnished to do so, subject to
79
+ the following conditions:
80
+
81
+ The above copyright notice and this permission notice shall be
82
+ included in all copies or substantial portions of the Software.
83
+
84
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
85
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
86
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
87
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
88
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
89
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
90
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,49 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+ require 'rubygems'
6
+
7
+ task :default => :test
8
+
9
+ desc 'Test the flickr plugin.'
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << 'lib'
12
+ t.libs << 'test'
13
+ t.pattern = 'test/**/*_test.rb'
14
+ t.verbose = true
15
+ end
16
+
17
+ desc 'Generate documentation for the flickr plugin.'
18
+ Rake::RDocTask.new(:rdoc) do |rdoc|
19
+ rdoc.rdoc_dir = 'rdoc'
20
+ rdoc.title = 'Flickr'
21
+ rdoc.options << '--line-numbers' << '--inline-source'
22
+ rdoc.rdoc_files.include('README.txt')
23
+ rdoc.rdoc_files.include('lib/**/*.rb')
24
+ end
25
+
26
+ spec = Gem::Specification.new do |s|
27
+ s.add_dependency('xml-simple', '>= 1.0.7')
28
+ s.name = 'flickr.rb'
29
+ s.version = "1.1.0"
30
+ s.platform = Gem::Platform::RUBY
31
+ s.summary = "An insanely easy interface to the Flickr photo-sharing service. Also available as a Rails plugin! By Scott Raymond. Maintainer: Patrick Plattes, Rafal Piekarski"
32
+ s.requirements << 'Flickr developers API key'
33
+ s.files = Dir.glob("**/*").delete_if { |item| item.include?(".git") || item[/^pkg/] }
34
+ s.require_path = 'lib'
35
+ s.author = "Scott Raymond, Patrick Plattes, Rafal Piekarski"
36
+ s.email = "ravbaker@gmail.com"
37
+ s.rubyforge_project = "flickr"
38
+ s.homepage = "http://github.com/RaVbaker/flickr/"
39
+ end
40
+
41
+ Rake::GemPackageTask.new(spec) do |pkg|
42
+ pkg.need_zip = true
43
+ pkg.need_tar = true
44
+ end
45
+
46
+ task "Dump Gemspec file"
47
+ task :debug do
48
+ puts spec.to_ruby
49
+ end
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,30 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{flickr}
3
+ s.version = "1.1.0"
4
+
5
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
+ s.authors = ["Scott Raymond, Patrick Plattes, Rafal Piekarski"]
7
+ s.autorequire = %q{flickr}
8
+ s.date = %q{2010-05-15}
9
+ s.email = %q{ravbaker@gmail.com}
10
+ s.files = ["History.txt", "LICENSE", "README.txt", "TODO", "lib/flickr.rb", "test/flickr_test.rb", "init.rb"]
11
+ s.homepage = %q{http://github.com/RaVbaker/flickr/}
12
+ s.require_paths = ["lib"]
13
+ s.requirements = ["Flickr developers API key"]
14
+ s.rubyforge_project = %q{flickr}
15
+ s.rubygems_version = %q{1.2.0}
16
+ s.summary = %q{An insanely easy interface to the Flickr photo-sharing service. Also available as a rails plugin! By Scott Raymond. Maintainer: Rafal Piekarski}
17
+
18
+ if s.respond_to? :specification_version then
19
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
20
+ s.specification_version = 2
21
+
22
+ if current_version >= 3 then
23
+ s.add_runtime_dependency(%q<xml-simple>, [">= 1.0.7"])
24
+ else
25
+ s.add_dependency(%q<xml-simple>, [">= 1.0.7"])
26
+ end
27
+ else
28
+ s.add_dependency(%q<xml-simple>, [">= 1.0.7"])
29
+ end
30
+ end
data/init.rb ADDED
@@ -0,0 +1,7 @@
1
+ # Require of whole library for rails plugins
2
+
3
+ flickr_config = "#{RAILS_ROOT}/config/flickr.api.yml"
4
+
5
+ require File.dirname(__FILE__)+'/lib/flickr.rb'
6
+
7
+ Flickr::Config.load_from_file flickr_config
@@ -0,0 +1,734 @@
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://api.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'. Other way is to use in Rails an config file
76
+ # RAILS_ROOT/config/flickr.api.yml and there use params as key/values even
77
+ # specified for every environment.
78
+ def initialize(api_key_or_params={})
79
+ @host = HOST_URL
80
+ @api = API_PATH
81
+ api_key_or_params={} if api_key_or_params.nil? # fix for nil value as argument
82
+ api_key_or_params = {:api_key => api_key_or_params} if api_key_or_params.is_a?(String)
83
+ api_key_or_params = Config.get if Config.parsed? and api_key_or_params.empty?
84
+ set_up_configuration api_key_or_params if api_key_or_params.is_a? Hash
85
+ end
86
+
87
+ def set_up_configuration api_key_or_params = {}
88
+ @api_key = api_key_or_params[:api_key]
89
+ @shared_secret = api_key_or_params[:shared_secret]
90
+ @auth_token = api_key_or_params[:auth_token]
91
+ end
92
+
93
+ # Gets authentication token given a Flickr frob, which is returned when user
94
+ # allows access to their account for the application with the api_key which
95
+ # made the request
96
+ def get_token_from(frob)
97
+ auth_response = request("auth.getToken", :frob => frob)['auth']
98
+ @auth_token = auth_response['token']
99
+ @user = User.new( 'id' => auth_response['user']['nsid'],
100
+ 'username' => auth_response['user']['username'],
101
+ 'name' => auth_response['user']['fullname'],
102
+ 'client' => self)
103
+ @auth_token
104
+ end
105
+
106
+ # Implements flickr.urls.lookupGroup and flickr.urls.lookupUser
107
+ def find_by_url(url)
108
+ response = urls_lookupUser('url'=>url) rescue urls_lookupGroup('url'=>url) rescue nil
109
+ (response['user']) ? User.new(response['user']['id'], nil, nil, nil, @api_key) : Group.new(response['group']['id'], @api_key) unless response.nil?
110
+ end
111
+
112
+ # Implements flickr.photos.getRecent and flickr.photos.search
113
+ def photos(*criteria)
114
+ not criteria.empty? and photos_search(*criteria) or recent
115
+ end
116
+
117
+ # flickr.photos.getRecent
118
+ # 100 newest photos from everyone
119
+ def recent
120
+ photos_request('photos.getRecent')
121
+ end
122
+
123
+ def photos_search(params={})
124
+ photos_request('photos.search', params)
125
+ end
126
+ alias_method :search, :photos_search
127
+
128
+ # Gets public photos with a given tag
129
+ def tag(tag)
130
+ photos('tags'=>tag)
131
+ end
132
+
133
+ # Implements flickr.people.findByEmail and flickr.people.findByUsername.
134
+ def users(lookup=nil)
135
+ user = people_findByEmail('find_email'=>lookup)['user'] rescue people_findByUsername('username'=>lookup)['user']
136
+ return User.new("id" => user["nsid"], "username" => user["username"], "client" => self)
137
+ end
138
+
139
+ # Implements flickr.groups.search
140
+ def groups(group_name, options={})
141
+ collection = groups_search({"text" => group_name}.merge(options))['groups']['group']
142
+ collection = [collection] if collection.is_a? Hash
143
+
144
+ collection.collect { |group| Group.new( "id" => group['nsid'],
145
+ "name" => group['name'],
146
+ "eighteenplus" => group['eighteenplus'],
147
+ "client" => self) }
148
+ end
149
+
150
+ def photoset(photoset_id)
151
+ Photoset.new(photoset_id, @api_key)
152
+ end
153
+
154
+ # Implements flickr.tags.getRelated
155
+ def related_tags(tag)
156
+ tags_getRelated('tag'=>tag)['tags']['tag']
157
+ end
158
+
159
+ # Implements flickr.photos.licenses.getInfo
160
+ def licenses
161
+ photos_licenses_getInfo['licenses']['license']
162
+ end
163
+
164
+ # Returns url for user to login in to Flickr to authenticate app for a user
165
+ def login_url(perms)
166
+ "http://flickr.com/services/auth/?api_key=#{@api_key}&perms=#{perms}&api_sig=#{signature_from('api_key'=>@api_key, 'perms' => perms)}"
167
+ end
168
+
169
+ # Implements everything else.
170
+ # Any method not defined explicitly will be passed on to the Flickr API,
171
+ # and return an XmlSimple document. For example, Flickr#test_echo is not
172
+ # defined, so it will pass the call to the flickr.test.echo method.
173
+ def method_missing(method_id, params={})
174
+ request(method_id.id2name.gsub(/_/, '.'), params)
175
+ end
176
+
177
+ # Does an HTTP GET on a given URL and returns the response body
178
+ def http_get(url)
179
+ Net::HTTP.get_response(URI.parse(url)).body.to_s
180
+ end
181
+
182
+ # Takes a Flickr API method name and set of parameters; returns an XmlSimple object with the response
183
+ def request(method, params={})
184
+ url = request_url(method, params)
185
+ response = XmlSimple.xml_in(http_get(url), { 'ForceArray' => false })
186
+ raise response['err']['msg'] if response['stat'] != 'ok'
187
+ response
188
+ end
189
+
190
+ # acts like request but returns a PhotoCollection (a list of Photo objects)
191
+ def photos_request(method, params={})
192
+ photos = request(method, params)
193
+ PhotoCollection.new(photos, @api_key)
194
+ end
195
+
196
+ # Builds url for Flickr API REST request from given the flickr method name
197
+ # (exclusing the 'flickr.' that begins each method call) and params (where
198
+ # applicable) which should be supplied as a Hash (e.g 'user_id' => "foo123")
199
+ def request_url(method, params={})
200
+ method = 'flickr.' + method
201
+ url = "#{@host}#{@api}/?api_key=#{@api_key}&method=#{method}"
202
+ params.merge!('api_key' => @api_key, 'method' => method, 'auth_token' => @auth_token)
203
+ signature = signature_from(params)
204
+
205
+ url = "#{@host}#{@api}/?" + params.merge('api_sig' => signature).collect { |k,v| "#{k}=" + CGI::escape(v.to_s) unless v.nil? }.compact.join("&")
206
+ end
207
+
208
+ def signature_from(params={})
209
+ return unless @shared_secret # don't both getting signature if no shared_secret
210
+ 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
211
+ return Digest::MD5.hexdigest("#{@shared_secret}#{request_str}")
212
+ end
213
+
214
+ # A collection of photos is returned as a PhotoCollection, a subclass of Array.
215
+ # This allows us to retain the pagination info returned by Flickr and make it
216
+ # accessible in a friendly way
217
+ class PhotoCollection < Array
218
+ attr_reader :page, :pages, :perpage, :total
219
+
220
+ # builds a PhotoCollection from given params, such as those returned from
221
+ # photos.search API call. Note all the info is contained in the value of
222
+ # the first (and only) key-value pair of the response. The key will vary
223
+ # depending on the original object the photos are related to (e.g 'photos',
224
+ # 'photoset', etc)
225
+ def initialize(photos_api_response={}, api_key={})
226
+ photos = photos_api_response.values.first
227
+ [ "page", "pages", "perpage", "total" ].each { |i| instance_variable_set("@#{i}", photos[i])}
228
+ collection = photos['photo'] || []
229
+ collection = [collection] if collection.is_a? Hash
230
+ collection.each { |photo| self << Photo.new(photo.delete('id'), api_key, photo) }
231
+ end
232
+ end
233
+
234
+ # This class supports external configuration
235
+ class Config
236
+
237
+ # Contains whole configuration for flickr API
238
+ @@configuration = {}
239
+
240
+ # Is true if configuration has been parsed
241
+ @@parsed = false
242
+
243
+ # parses file and prepare @@configuration for access from outside to fetch configuration hash
244
+ def self.load_from_file file
245
+ return false unless File.exist?(file)
246
+ @@configuration = YAML.load(ERB.new(File.read(file)).result)
247
+ @@parsed = true
248
+ parse_in_rails_env!
249
+ end
250
+
251
+ # Excludes specific configuration for choosed environment in Rails
252
+ def self.parse_in_rails_env!
253
+ @@configuration = @@configuration[RAILS_ENV].symbolize_keys if defined? RAILS_ENV
254
+ end
255
+
256
+ # Returns configuration Hash
257
+ def self.get
258
+ @@configuration
259
+ end
260
+
261
+ # Returns true if configuration has been parsed
262
+ def self.parsed?
263
+ @@parsed
264
+ end
265
+ end
266
+
267
+ # Todo:
268
+ # logged_in?
269
+ # if logged in:
270
+ # flickr.blogs.getList
271
+ # flickr.favorites.add
272
+ # flickr.favorites.remove
273
+ # flickr.groups.browse
274
+ # flickr.photos.getCounts
275
+ # flickr.photos.getNotInSet
276
+ # flickr.photos.getUntagged
277
+ # flickr.photosets.create
278
+ # flickr.photosets.orderSets
279
+ # flickr.test.login
280
+ # uploading
281
+ class User
282
+
283
+ attr_reader :client, :id, :name, :location, :photos_url, :url, :count, :firstdate, :firstdatetaken
284
+
285
+ # A Flickr::User can be instantiated in two ways. The old (deprecated)
286
+ # method is with an ordered series of values. The new method is with a
287
+ # params Hash, which is easier when a variable number of params are
288
+ # supplied, which is the case here, and also avoids having to constantly
289
+ # supply nil values for the email and password, which are now irrelevant
290
+ # as authentication is no longer done this way.
291
+ # An associated flickr client will also be generated if an api key is
292
+ # passed among the arguments or in the params hash. Alternatively, and
293
+ # most likely, an existing client object may be passed in the params hash
294
+ # (e.g. 'client' => some_existing_flickr_client_object), and this is
295
+ # what happends when users are initlialized as the result of a method
296
+ # called on the flickr client (e.g. flickr.users)
297
+ def initialize(id_or_params_hash=nil, username=nil, email=nil, password=nil, api_key={})
298
+ if id_or_params_hash.is_a?(Hash)
299
+ id_or_params_hash.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
300
+ else
301
+ @id = id_or_params_hash
302
+ @username = username
303
+ @email = email
304
+ @password = password
305
+ @api_key = api_key
306
+ end
307
+ @client ||= Flickr.new(:api_key => @api_key, :shared_secret => @shared_secret, :auth_token => @auth_token) if @api_key
308
+ end
309
+
310
+ def username
311
+ @username.nil? ? getInfo.username : @username
312
+ end
313
+ def name
314
+ @name.nil? ? getInfo.name : @name
315
+ end
316
+ def location
317
+ @location.nil? ? getInfo.location : @location
318
+ end
319
+ def count
320
+ @count.nil? ? getInfo.count : @count
321
+ end
322
+ def firstdate
323
+ @firstdate.nil? ? getInfo.firstdate : @firstdate
324
+ end
325
+ def firstdatetaken
326
+ @firstdatetaken.nil? ? getInfo.firstdatetaken : @firstdatetaken
327
+ end
328
+
329
+ # Builds url for user's photos page as per
330
+ # http://www.flickr.com/services/api/misc.urls.html
331
+ def photos_url
332
+ "http://www.flickr.com/photos/#{id}/"
333
+ end
334
+
335
+ # Builds url for user's profile page as per
336
+ # http://www.flickr.com/services/api/misc.urls.html
337
+ def url
338
+ "http://www.flickr.com/people/#{id}/"
339
+ end
340
+
341
+ def pretty_url
342
+ @pretty_url ||= @client.urls_getUserProfile('user_id'=>@id)['user']['url']
343
+ end
344
+
345
+ # Implements flickr.people.getPublicGroups
346
+ def groups
347
+ collection = @client.people_getPublicGroups('user_id'=>@id)['groups']['group']
348
+ collection = [collection] if collection.is_a? Hash
349
+ collection.collect { |group| Group.new( "id" => group['nsid'],
350
+ "name" => group['name'],
351
+ "eighteenplus" => group['eighteenplus'],
352
+ "client" => @client) }
353
+ end
354
+
355
+ # Implements flickr.people.getPublicPhotos. Options hash allows you to add
356
+ # extra restrictions as per flickr.people.getPublicPhotos docs, e.g.
357
+ # user.photos('per_page' => '25', 'extras' => 'date_taken')
358
+ def photos(options={})
359
+ @client.photos_request('people.getPublicPhotos', {'user_id' => @id}.merge(options))
360
+ # what about non-public photos?
361
+ end
362
+
363
+ # Gets photos with a given tag
364
+ def tag(tag)
365
+ @client.photos('user_id'=>@id, 'tags'=>tag)
366
+ end
367
+
368
+ # Implements flickr.contacts.getPublicList and flickr.contacts.getList
369
+ def contacts
370
+ @client.contacts_getPublicList('user_id'=>@id)['contacts']['contact'].collect { |contact| User.new(contact['nsid'], contact['username'], nil, nil, @api_key) }
371
+ #or
372
+ end
373
+
374
+ # Implements flickr.favorites.getPublicList
375
+ def favorites
376
+ @client.photos_request('favorites.getPublicList', 'user_id' => @id)
377
+ end
378
+
379
+ # Implements flickr.photosets.getList
380
+ def photosets
381
+ @client.photosets_getList('user_id'=>@id)['photosets']['photoset'].collect { |photoset| Photoset.new(photoset['id'], @api_key) }
382
+ end
383
+
384
+ # Implements flickr.tags.getListUser
385
+ def tags
386
+ @client.tags_getListUser('user_id'=>@id)['who']['tags']['tag'].collect { |tag| tag }
387
+ end
388
+
389
+ # Implements flickr.tags.getListUserPopular
390
+ def popular_tags(count = 10)
391
+ @client.tags_getListUserPopular('user_id'=>@id, 'count'=> count)['who']['tags']['tag'].each { |tag_score| tag_score["tag"] = tag_score.delete("content") }
392
+ end
393
+
394
+ # Implements flickr.photos.getContactsPublicPhotos and flickr.photos.getContactsPhotos
395
+ def contactsPhotos
396
+ @client.photos_request('photos.getContactsPublicPhotos', 'user_id' => @id)
397
+ end
398
+
399
+ def to_s
400
+ @name
401
+ end
402
+
403
+ private
404
+
405
+ # Implements flickr.people.getInfo, flickr.urls.getUserPhotos, and flickr.urls.getUserProfile
406
+ def getInfo
407
+ info = @client.people_getInfo('user_id'=>@id)['person']
408
+ @username = info['username']
409
+ @name = info['realname']
410
+ @location = info['location']
411
+ @count = info['photos']['count']
412
+ @firstdate = info['photos']['firstdate']
413
+ @firstdatetaken = info['photos']['firstdatetaken']
414
+ self
415
+ end
416
+
417
+ end
418
+
419
+ class Photo
420
+
421
+ attr_reader :id, :client, :title
422
+
423
+ def initialize(id=nil, api_key={}, extra_params={})
424
+ @id = id
425
+ @api_key = api_key
426
+ extra_params.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
427
+ @client = Flickr.new @api_key
428
+ end
429
+
430
+ # Allows access to all photos instance variables through hash like
431
+ # interface, e.g. photo["datetaken"] returns @datetaken instance
432
+ # variable. Useful for accessing any weird and wonderful parameter
433
+ # that may have been returned by Flickr when finding the photo,
434
+ # e.g. those returned by the extras argument in
435
+ # flickr.people.getPublicPhotos
436
+ def [](param_name)
437
+ instance_variable_get("@#{param_name}")
438
+ end
439
+
440
+ def title
441
+ @title.nil? ? getInfo("title") : @title
442
+ end
443
+
444
+ # Returns the owner of the photo as a Flickr::User. If we have no info
445
+ # about the owner, we make an API call to get it. If we already have
446
+ # the owner's id, create a user based on that. Either way, we cache the
447
+ # result so we don't need to check again
448
+ def owner
449
+ case @owner
450
+ when Flickr::User
451
+ @owner
452
+ when String
453
+ @owner = Flickr::User.new(@owner, nil, nil, nil, @api_key)
454
+ else
455
+ getInfo("owner")
456
+ end
457
+ end
458
+
459
+ def server
460
+ @server.nil? ? getInfo("server") : @server
461
+ end
462
+
463
+ def isfavorite
464
+ @isfavorite.nil? ? getInfo("isfavorite") : @isfavorite
465
+ end
466
+
467
+ def license
468
+ @license.nil? ? getInfo("license") : @license
469
+ end
470
+
471
+ def rotation
472
+ @rotation.nil? ? getInfo("rotation") : @rotation
473
+ end
474
+
475
+ def description
476
+ @description || getInfo("description")
477
+ end
478
+
479
+ def notes
480
+ @notes.nil? ? getInfo("notes") : @notes
481
+ end
482
+
483
+ # Returns the URL for the photo size page
484
+ # defaults to 'Medium'
485
+ # other valid sizes are in the VALID_SIZES hash
486
+ def size_url(size='Medium')
487
+ uri_for_photo_from_self(size) || sizes(size)['url']
488
+ end
489
+
490
+ # converts string or symbol size to a capitalized string
491
+ def normalize_size(size)
492
+ size ? size.to_s.capitalize : size
493
+ end
494
+
495
+ # the URL for the main photo page
496
+ # if getInfo has already been called, this will return the pretty url
497
+ #
498
+ # for historical reasons, an optional size can be given
499
+ # 'Medium' returns the regular url; any other size returns a size page
500
+ # use size_url instead
501
+ def url(size = nil)
502
+ if normalize_size(size) != 'Medium'
503
+ size_url(size)
504
+ else
505
+ @url || uri_for_photo_from_self
506
+ end
507
+ end
508
+
509
+ # the 'pretty' url for a photo
510
+ # (if the user has set up a custom name)
511
+ # eg, http://flickr.com/photos/granth/2584402507/ instead of
512
+ # http://flickr.com/photos/23386158@N00/2584402507/
513
+ def pretty_url
514
+ @url || getInfo("pretty_url")
515
+ end
516
+
517
+ # Returns the URL for the image (default or any specified size)
518
+ def source(size='Medium')
519
+ image_source_uri_from_self(size) || sizes(size)['source']
520
+ end
521
+
522
+ # Returns the photo file data itself, in any specified size. Example: File.open(photo.title, 'w') { |f| f.puts photo.file }
523
+ def file(size='Medium')
524
+ Net::HTTP.get_response(URI.parse(source(size))).body
525
+ end
526
+
527
+ # Unique filename for the image, based on the Flickr NSID
528
+ def filename
529
+ "#{@id}.jpg"
530
+ end
531
+
532
+ # Implements flickr.photos.getContext
533
+ def context
534
+ context = @client.photos_getContext('photo_id'=>@id)
535
+ @previousPhoto = Photo.new(context['prevphoto'].delete('id'), @api_key, context['prevphoto']) if context['prevphoto']['id']!='0'
536
+ @nextPhoto = Photo.new(context['nextphoto'].delete('id'), @api_key, context['nextphoto']) if context['nextphoto']['id']!='0'
537
+ return [@previousPhoto, @nextPhoto]
538
+ end
539
+
540
+ # Implements flickr.photos.getExif
541
+ def exif
542
+ @client.photos_getExif('photo_id'=>@id)['photo']
543
+ end
544
+
545
+ # Implements flickr.photos.getPerms
546
+ def permissions
547
+ @client.photos_getPerms('photo_id'=>@id)['perms']
548
+ end
549
+
550
+ # Implements flickr.photos.getSizes
551
+ def sizes(size=nil)
552
+ size = normalize_size(size)
553
+ sizes = @client.photos_getSizes('photo_id'=>@id)['sizes']['size']
554
+ sizes = sizes.find{|asize| asize['label']==size} if size
555
+ return sizes
556
+ end
557
+
558
+ # flickr.tags.getListPhoto
559
+ def tags
560
+ @client.tags_getListPhoto('photo_id'=>@id)['photo']['tags']
561
+ end
562
+
563
+ # Implements flickr.photos.notes.add
564
+ def add_note(note)
565
+ end
566
+
567
+ # Implements flickr.photos.setDates
568
+ def dates=(dates)
569
+ end
570
+
571
+ # Implements flickr.photos.setPerms
572
+ def perms=(perms)
573
+ end
574
+
575
+ # Implements flickr.photos.setTags
576
+ def tags=(tags)
577
+ end
578
+
579
+ # Implements flickr.photos.setMeta
580
+ def title=(title)
581
+ end
582
+ def description=(title)
583
+ end
584
+
585
+ # Implements flickr.photos.addTags
586
+ def add_tag(tag)
587
+ end
588
+
589
+ # Implements flickr.photos.removeTag
590
+ def remove_tag(tag)
591
+ end
592
+
593
+ # Implements flickr.photos.transform.rotate
594
+ def rotate
595
+ end
596
+
597
+ # Implements flickr.blogs.postPhoto
598
+ def postToBlog(blog_id, title='', description='')
599
+ @client.blogs_postPhoto('photo_id'=>@id, 'title'=>title, 'description'=>description)
600
+ end
601
+
602
+ # Implements flickr.photos.notes.delete
603
+ def deleteNote(note_id)
604
+ end
605
+
606
+ # Implements flickr.photos.notes.edit
607
+ def editNote(note_id)
608
+ end
609
+
610
+ # Converts the Photo to a string by returning its title
611
+ def to_s
612
+ title
613
+ end
614
+
615
+ private
616
+
617
+ # Implements flickr.photos.getInfo
618
+ def getInfo(attrib="")
619
+ return instance_variable_get("@#{attrib}") if @got_info
620
+ info = @client.photos_getInfo('photo_id'=>@id)['photo']
621
+ @got_info = true
622
+ info.each { |k,v| instance_variable_set("@#{k}", v)}
623
+ @owner = User.new(info['owner']['nsid'], info['owner']['username'], nil, nil, @api_key)
624
+ @tags = info['tags']['tag']
625
+ @notes = info['notes']['note']#.collect { |note| Note.new(note.id) }
626
+ @url = info['urls']['url']['content'] # assumes only one url
627
+ instance_variable_get("@#{attrib}")
628
+ end
629
+
630
+ # Builds source uri of image from params (often returned from other
631
+ # methods, e.g. User#photos). As specified at:
632
+ # http://www.flickr.com/services/api/misc.urls.html. If size is given
633
+ # should be one the keys in the VALID_SIZES hash, i.e.
634
+ # "Square", "Thumbnail", "Medium", "Large", "Original", "Small" (These
635
+ # are the values returned by flickr.photos.getSizes).
636
+ # If no size is given the uri for "Medium"-size image, i.e. with width
637
+ # of 500 is returned
638
+ # TODO: Handle "Original" size
639
+ def image_source_uri_from_self(size=nil)
640
+ return unless @farm&&@server&&@id&&@secret
641
+ s_size = VALID_SIZES[normalize_size(size)] # get the short letters array corresponding to the size
642
+ s_size = s_size&&s_size[0] # the first element of this array is used to build the source uri
643
+ if s_size.nil?
644
+ "http://farm#{@farm}.static.flickr.com/#{@server}/#{@id}_#{@secret}.jpg"
645
+ else
646
+ "http://farm#{@farm}.static.flickr.com/#{@server}/#{@id}_#{@secret}_#{s_size}.jpg"
647
+ end
648
+ end
649
+
650
+ # Builds uri of Flickr page for photo. By default returns the main
651
+ # page for the photo, but if passed a size will return the simplified
652
+ # flickr page featuring the given size of the photo
653
+ # TODO: Handle "Original" size
654
+ def uri_for_photo_from_self(size=nil)
655
+ return unless @owner&&@id
656
+ size = normalize_size(size)
657
+ s_size = VALID_SIZES[size] # get the short letters array corresponding to the size
658
+ 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
659
+ "http://www.flickr.com/photos/#{owner.id}/#{@id}" + (s_size ? "/sizes/#{s_size}/" : "")
660
+ end
661
+ end
662
+
663
+ # Todo:
664
+ # flickr.groups.pools.add
665
+ # flickr.groups.pools.getContext
666
+ # flickr.groups.pools.getGroups
667
+ # flickr.groups.pools.getPhotos
668
+ # flickr.groups.pools.remove
669
+ class Group
670
+ attr_reader :id, :client, :description, :name, :eighteenplus, :members, :online, :privacy, :url#, :chatid, :chatcount
671
+
672
+ def initialize(id_or_params_hash=nil, api_key={})
673
+ if id_or_params_hash.is_a?(Hash)
674
+ id_or_params_hash.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
675
+ else
676
+ @id = id_or_params_hash
677
+ @api_key = api_key
678
+ @client = Flickr.new @api_key
679
+ end
680
+ end
681
+
682
+ # Implements flickr.groups.getInfo and flickr.urls.getGroup
683
+ # private, once we can call it as needed
684
+ def getInfo
685
+ info = @client.groups_getInfo('group_id'=>@id)['group']
686
+ @name = info['name']
687
+ @members = info['members']
688
+ @online = info['online']
689
+ @privacy = info['privacy']
690
+ # @chatid = info['chatid']
691
+ # @chatcount = info['chatcount']
692
+ @url = @client.urls_getGroup('group_id'=>@id)['group']['url']
693
+ self
694
+ end
695
+
696
+ end
697
+
698
+ # Todo:
699
+ # flickr.photosets.delete
700
+ # flickr.photosets.editMeta
701
+ # flickr.photosets.editPhotos
702
+ # flickr.photosets.getContext
703
+ # flickr.photosets.getInfo
704
+ # flickr.photosets.getPhotos
705
+ class Photoset
706
+
707
+ attr_reader :id, :client, :owner, :primary, :photos, :title, :description, :url
708
+
709
+ def initialize(id=nil, api_key={})
710
+ @id = id
711
+ @api_key = api_key
712
+ @client = Flickr.new @api_key
713
+ end
714
+
715
+ # Implements flickr.photosets.getInfo
716
+ # private, once we can call it as needed
717
+ def getInfo
718
+ info = @client.photosets_getInfo('photoset_id'=>@id)['photoset']
719
+ @owner = User.new(info['owner'], nil, nil, nil, @api_key)
720
+ @primary = info['primary']
721
+ @photos = info['photos']
722
+ @title = info['title']
723
+ @description = info['description']
724
+ @url = "http://www.flickr.com/photos/#{@owner.getInfo.username}/sets/#{@id}/"
725
+ self
726
+ end
727
+
728
+ def getPhotos
729
+ photosetPhotos = @client.photos_request('photosets.getPhotos', {'photoset_id' => @id})
730
+ end
731
+
732
+ end
733
+
734
+ end