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