flickr.rb 1.1.0

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.
@@ -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