rflickr 2006.02.01
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/GETTING-STARTED +40 -0
- data/lib/flickr.rb +19 -0
- data/lib/flickr/auth.rb +76 -0
- data/lib/flickr/base.rb +793 -0
- data/lib/flickr/blogs.rb +30 -0
- data/lib/flickr/contacts.rb +48 -0
- data/lib/flickr/favorites.rb +59 -0
- data/lib/flickr/groups.rb +82 -0
- data/lib/flickr/interestingness.rb +19 -0
- data/lib/flickr/licenses.rb +23 -0
- data/lib/flickr/notes.rb +24 -0
- data/lib/flickr/people.rb +79 -0
- data/lib/flickr/photos.rb +285 -0
- data/lib/flickr/photosets.rb +104 -0
- data/lib/flickr/pools.rb +70 -0
- data/lib/flickr/reflection.rb +89 -0
- data/lib/flickr/tags.rb +59 -0
- data/lib/flickr/test.rb +19 -0
- data/lib/flickr/transform.rb +9 -0
- data/lib/flickr/upload.rb +205 -0
- data/lib/flickr/urls.rb +49 -0
- data/sample/album_test.rb +30 -0
- data/sample/comics-reorder.rb +16 -0
- data/sample/loadr.rb +80 -0
- data/sample/relatedness.rb +65 -0
- data/sample/setdumpr.rb +46 -0
- metadata +73 -0
data/GETTING-STARTED
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
First you'll need to run 'ruby setup.rb' to install rflickr to your
|
2
|
+
library directory or if you're using gems, 'gem install rflickr --remote'.
|
3
|
+
If installing on Unix, this will probably need to be done as root.
|
4
|
+
|
5
|
+
Once you've got it installed, there are a few example applications in the
|
6
|
+
samples directory. You need to first get an API key as detailed here:
|
7
|
+
|
8
|
+
http://www.flickr.com/services/api/misc.api_keys.html
|
9
|
+
|
10
|
+
You'll need to authorize your application's API key
|
11
|
+
from each account which wants to use it.
|
12
|
+
|
13
|
+
Here is a sample you can use to get started:
|
14
|
+
|
15
|
+
flickr = Flickr.new(token_cache_file,MY_APPLICATION_KEY,MY_SHARED_SECRET)
|
16
|
+
unless flickr.auth.token
|
17
|
+
flickr.auth.getFrob
|
18
|
+
url = @flickr.auth.login_link
|
19
|
+
puts "You must visit #{url} to authorize this application. Press enter"+
|
20
|
+
" when you have done so. This is the only time you will have to do this."
|
21
|
+
gets
|
22
|
+
flickr.auth.getToken
|
23
|
+
flickr.auth.cache_token
|
24
|
+
end
|
25
|
+
|
26
|
+
<... do flickr stuff now ...>
|
27
|
+
|
28
|
+
The general idea is that once you cache the token, the user will not
|
29
|
+
have to reauthorize your application unless the file where the token
|
30
|
+
is cached (the first argument to the new method on Flickr) is deleted.
|
31
|
+
When you create a new Flickr and pass in a file path to the cached
|
32
|
+
token, the API will attempt to load the token from the file. If found
|
33
|
+
and loaded, the Flickr instance's auth.token is present. If the token
|
34
|
+
could not be found in the file specified, then the instance's
|
35
|
+
auth.token will be nil, and you will have to have the user authorize
|
36
|
+
the application before it can be used.
|
37
|
+
|
38
|
+
For convenience, you can set your API_KEY and SHARED_SECRETE in the
|
39
|
+
installed version of flickr/base.rb rather than including it in every
|
40
|
+
application.
|
data/lib/flickr.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'flickr/auth'
|
2
|
+
require 'flickr/base'
|
3
|
+
require 'flickr/blogs'
|
4
|
+
require 'flickr/contacts'
|
5
|
+
require 'flickr/favorites'
|
6
|
+
require 'flickr/groups'
|
7
|
+
require 'flickr/licenses'
|
8
|
+
require 'flickr/notes'
|
9
|
+
require 'flickr/people'
|
10
|
+
require 'flickr/photos'
|
11
|
+
require 'flickr/photosets'
|
12
|
+
require 'flickr/pools'
|
13
|
+
require 'flickr/reflection'
|
14
|
+
require 'flickr/test'
|
15
|
+
require 'flickr/transform'
|
16
|
+
require 'flickr/upload'
|
17
|
+
require 'flickr/urls'
|
18
|
+
require 'flickr/tags'
|
19
|
+
require 'flickr/interestingness'
|
data/lib/flickr/auth.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'flickr/base'
|
4
|
+
|
5
|
+
class Flickr::Auth < Flickr::APIBase
|
6
|
+
attr_accessor :cache_file, :token
|
7
|
+
|
8
|
+
def clear_cache
|
9
|
+
@token = nil
|
10
|
+
@frob = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(flickr,cache_file=nil)
|
14
|
+
super(flickr)
|
15
|
+
@frob = nil
|
16
|
+
@token = nil
|
17
|
+
@cache_file = cache_file
|
18
|
+
if @cache_file && File.exists?(@cache_file)
|
19
|
+
load_token
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def login_link(perms='delete')
|
24
|
+
args={ 'api_key' => @flickr.api_key, 'perms' => perms}
|
25
|
+
args['frob'] = self.frob
|
26
|
+
args['api_sig'] = @flickr.sign(args)
|
27
|
+
return "http://flickr.com/services/auth/?"+
|
28
|
+
args.to_a.map{|arr| arr.join('=')}.join('&')
|
29
|
+
end
|
30
|
+
|
31
|
+
def frob=(frob) @frob = frob end
|
32
|
+
def frob() return @frob || getFrob end
|
33
|
+
|
34
|
+
def cache_token
|
35
|
+
File.open(@cache_file,'w'){ |f| f.write @token.to_xml } if token
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_token
|
39
|
+
token = nil
|
40
|
+
File.open(@cache_file,'r'){ |f| token = f.read }
|
41
|
+
# Dirt stupid check to see if it's probably XML or
|
42
|
+
# not. If it is, then we don't call checkToken.
|
43
|
+
#
|
44
|
+
# Backwwards compatible with old token storage.
|
45
|
+
@token = token.include?('<') ?
|
46
|
+
Flickr::Token.from_xml(REXML::Document.new(token)) :
|
47
|
+
@token = checkToken(token)
|
48
|
+
end
|
49
|
+
|
50
|
+
def getToken(frob=nil)
|
51
|
+
frob ||= @frob
|
52
|
+
res=@flickr.call_unauth_method('flickr.auth.getToken',
|
53
|
+
'frob'=>frob)
|
54
|
+
@token = Flickr::Token.from_xml(res)
|
55
|
+
end
|
56
|
+
|
57
|
+
def getFullToken(mini_token)
|
58
|
+
res = flickr.call_unauth_method('flickr.auth.getFullToken',
|
59
|
+
'mini_token' => mini_token)
|
60
|
+
@token = Flickr::Token.from_xml(res)
|
61
|
+
end
|
62
|
+
|
63
|
+
def getFrob
|
64
|
+
doc = @flickr.call_unauth_method('flickr.auth.getFrob')
|
65
|
+
@frob = doc.elements['/frob'].text
|
66
|
+
return @frob
|
67
|
+
end
|
68
|
+
|
69
|
+
def checkToken(token=nil)
|
70
|
+
token ||= @token
|
71
|
+
token = token.token if token.class == Flickr::Token
|
72
|
+
res = @flickr.call_unauth_method('flickr.auth.checkToken',
|
73
|
+
'auth_token' => token)
|
74
|
+
@token = Flickr::Token.from_xml(res)
|
75
|
+
end
|
76
|
+
end
|
data/lib/flickr/base.rb
ADDED
@@ -0,0 +1,793 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
## Structure this class hierarchy the way the Flickr API is structured.
|
4
|
+
## Flickr::Auth and so on. At least think about whether it makes sense or
|
5
|
+
## not.
|
6
|
+
|
7
|
+
require 'xmlrpc/client'
|
8
|
+
require 'md5'
|
9
|
+
require 'rexml/document'
|
10
|
+
require 'parsedate'
|
11
|
+
|
12
|
+
class Flickr
|
13
|
+
API_KEY=''
|
14
|
+
SHARED_SECRET=''
|
15
|
+
|
16
|
+
attr_reader :api_key
|
17
|
+
attr_accessor :async, :debug, :caching, :auth_mode
|
18
|
+
|
19
|
+
############################### CACHE ACCESSORS ###########################
|
20
|
+
def ticket_cache_lookup(id) @ticket_by_id[id] if @caching end
|
21
|
+
|
22
|
+
def ticket_cache_store(ticket)
|
23
|
+
@ticket_by_id[ticket.id] = ticket if @caching
|
24
|
+
end
|
25
|
+
|
26
|
+
def person_cache_lookup(nsid) @person_by_nsid[nsid] if @caching end
|
27
|
+
|
28
|
+
def person_cache_store(person)
|
29
|
+
@person_by_nsid[person.nsid] = person if @caching
|
30
|
+
end
|
31
|
+
|
32
|
+
def photo_cache_lookup(id) @photo_by_id[id] if @caching end
|
33
|
+
|
34
|
+
def photo_cache_store(photo)
|
35
|
+
@photo_by_id[photo.id] = photo if @caching
|
36
|
+
end
|
37
|
+
|
38
|
+
def license_cache_lookup() @license_cache if @caching end
|
39
|
+
|
40
|
+
def license_cache_store(licenses)
|
41
|
+
@license_cache = licenses if @caching
|
42
|
+
end
|
43
|
+
|
44
|
+
def blog_cache_lookup() @blog_cache if @caching end
|
45
|
+
|
46
|
+
def blog_cache_store(blogs) @blog_cache = blogs if @caching end
|
47
|
+
|
48
|
+
def photoset_cache_lookup(id) @photoset_by_id[id] if @caching end
|
49
|
+
|
50
|
+
def photoset_cache_store(set)
|
51
|
+
@photoset_by_id[set.id] = set if @caching
|
52
|
+
end
|
53
|
+
|
54
|
+
def photopool_cache_lookup(id) @photopool_by_id[id] if @caching end
|
55
|
+
|
56
|
+
def photopool_cache_store(pool)
|
57
|
+
@photopool_by_id[pool.id] = pool if @caching
|
58
|
+
end
|
59
|
+
|
60
|
+
def group_cache_lookup(id) @group_by_id[id] if @caching end
|
61
|
+
|
62
|
+
def group_cache_store(group)
|
63
|
+
@group_by_id[group.id] = group if @caching
|
64
|
+
end
|
65
|
+
############################################################################
|
66
|
+
|
67
|
+
def debug(*args) $stderr.puts(sprintf(*args)) if @debug end
|
68
|
+
|
69
|
+
def Flickr.todo
|
70
|
+
[
|
71
|
+
'Refactor, especially more Class.from_xml methods',
|
72
|
+
'More logical OO design, wrap the API methods to make transparent',
|
73
|
+
'Class & method documentation',
|
74
|
+
'Unit tests',
|
75
|
+
'Implement missing methods (see flickr.reflection.missing_methods)'
|
76
|
+
]
|
77
|
+
end
|
78
|
+
def todo()
|
79
|
+
Flickr.todo+reflection.missing_methods.map{|m| 'Implement '+m}
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def initialize(token_cache=nil,api_key=API_KEY,
|
84
|
+
shared_secret=SHARED_SECRET,
|
85
|
+
endpoint='http://www.flickr.com/services/xmlrpc/')
|
86
|
+
@async = false
|
87
|
+
@caching = true
|
88
|
+
@auth_mode = true
|
89
|
+
@api_key=api_key
|
90
|
+
@shared_secret=shared_secret
|
91
|
+
@token_cache = token_cache
|
92
|
+
@endpoint=endpoint
|
93
|
+
proto,host,port,path,user,pass=parse_url(@endpoint)
|
94
|
+
raise ProtoUnknownError.new("Unhandled protocol '#{proto}'") if
|
95
|
+
proto.downcase != 'http'
|
96
|
+
@client=XMLRPC::Client.new(host,path,port)
|
97
|
+
clear_cache
|
98
|
+
end
|
99
|
+
|
100
|
+
def clear_cache()
|
101
|
+
@auth = nil
|
102
|
+
@blogs = nil
|
103
|
+
@contacts = nil
|
104
|
+
@favorites = nil
|
105
|
+
@groups = nil
|
106
|
+
@interestingness = nil
|
107
|
+
@reflection = nil
|
108
|
+
@people = nil
|
109
|
+
@photos = nil
|
110
|
+
@photosets = nil
|
111
|
+
@test = nil
|
112
|
+
@urls = nil
|
113
|
+
|
114
|
+
@ticket_by_id = {}
|
115
|
+
@person_by_nsid = {}
|
116
|
+
@photo_by_id = {}
|
117
|
+
@photoset_by_id = {}
|
118
|
+
@photopool_by_id = {}
|
119
|
+
@group_by_id = {}
|
120
|
+
@license_cache = nil
|
121
|
+
@blog_cache = nil
|
122
|
+
end
|
123
|
+
|
124
|
+
def auth() @auth ||= Auth.new(self,@token_cache) end
|
125
|
+
def blogs() @blogs ||= Blogs.new(self) end
|
126
|
+
def contacts() @contacts ||= Contacts.new(self) end
|
127
|
+
def favorites() @favorites ||= Favorites.new(self) end
|
128
|
+
def groups() @groups ||= Groups.new(self) end
|
129
|
+
def people() @people ||= People.new(self) end
|
130
|
+
def photos() @photos ||= Photos.new(self) end
|
131
|
+
def photosets() @photosets ||= PhotoSets.new(self) end
|
132
|
+
def reflection() @reflection ||= Reflection.new(self) end
|
133
|
+
def test() @test ||= Test.new(self) end
|
134
|
+
def urls() @urls ||= Urls.new(self) end
|
135
|
+
def tags() @tags ||= Tags.new(self) end
|
136
|
+
def interestingness() @interestingness ||= Interestingness.new(self) end
|
137
|
+
|
138
|
+
def call_method(method,args={})
|
139
|
+
@auth_mode ? call_auth_method(method,args) :
|
140
|
+
call_unauth_method(method,args)
|
141
|
+
end
|
142
|
+
|
143
|
+
def call_unauth_method(method,args={})
|
144
|
+
debug('%s(%s)', method, args.inspect)
|
145
|
+
tries = 3
|
146
|
+
args = args.dup
|
147
|
+
args['api_key'] = @api_key
|
148
|
+
api_sig=sign(args)
|
149
|
+
args['api_sig']=api_sig
|
150
|
+
begin
|
151
|
+
tries -= 1;
|
152
|
+
str = @async ? @client.call_async(method,args) :
|
153
|
+
@client.call(method,args)
|
154
|
+
debug('RETURN: %s',str)
|
155
|
+
return REXML::Document.new(str)
|
156
|
+
rescue Timeout::Error => te
|
157
|
+
$stderr.puts "Timed out, will try #{tries} more times."
|
158
|
+
if tries > 0
|
159
|
+
retry
|
160
|
+
else
|
161
|
+
raise te
|
162
|
+
end
|
163
|
+
rescue REXML::ParseException => pe
|
164
|
+
return REXML::Document.new('<rsp>'+str+'</rsp>').
|
165
|
+
elements['/rsp']
|
166
|
+
rescue XMLRPC::FaultException => fe
|
167
|
+
$stderr.puts "ERR: #{fe.faultString} (#{fe.faultCode})"
|
168
|
+
raise fe
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def call_auth_method(method,args={})
|
173
|
+
at = args['auth_token']
|
174
|
+
args['auth_token'] ||= auth.token.token
|
175
|
+
res = call_unauth_method(method,args)
|
176
|
+
args.delete('auth_token') unless at
|
177
|
+
return res
|
178
|
+
end
|
179
|
+
|
180
|
+
def sign(args)
|
181
|
+
return MD5.md5(@shared_secret+args.sort.flatten.join).to_s
|
182
|
+
end
|
183
|
+
|
184
|
+
def parse_url(url)
|
185
|
+
url =~ /([^:]+):\/\/([^\/]*)(.*)/
|
186
|
+
proto = $1.to_s
|
187
|
+
hostplus = $2.to_s
|
188
|
+
path = $3.to_s
|
189
|
+
|
190
|
+
hostplus =~ /(?:(.*)@)?(.*)/
|
191
|
+
userpass = $1
|
192
|
+
hostport = $2
|
193
|
+
user,pass = userpass.to_s.split(':',2)
|
194
|
+
host,port = hostport.to_s.split(':',2)
|
195
|
+
port = port ? port.to_i : 80
|
196
|
+
|
197
|
+
return proto,host,port,path,user,pass
|
198
|
+
end
|
199
|
+
|
200
|
+
def mysql_datetime(time) time.strftime('%Y-%m-%d %H:%M:%S') end
|
201
|
+
def mysql_date(time) time.strftime('%Y-%m-%d') end
|
202
|
+
end
|
203
|
+
|
204
|
+
class Flickr::APIBase
|
205
|
+
attr_reader :flickr
|
206
|
+
|
207
|
+
def initialize(flickr) @flickr = flickr end
|
208
|
+
end
|
209
|
+
|
210
|
+
class Flickr::Token
|
211
|
+
attr_reader :token, :perms, :user
|
212
|
+
|
213
|
+
def initialize(token, perms, user)
|
214
|
+
@token = token
|
215
|
+
@perms = perms
|
216
|
+
@user = user
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.from_xml(xml, flickr=nil)
|
220
|
+
token = xml.elements['/auth/token'].text
|
221
|
+
perms = xml.elements['/auth/perms'].text.intern
|
222
|
+
user = xml.elements['/auth/user']
|
223
|
+
nsid = user.attributes['nsid']
|
224
|
+
username = user.attributes['username']
|
225
|
+
fullname = user.attributes['fullname']
|
226
|
+
|
227
|
+
p = flickr.person_cache_lookup(nsid) if flickr
|
228
|
+
p ||= Flickr::Person.new(flickr,nsid,username)
|
229
|
+
p.realname=fullname
|
230
|
+
flickr.person_cache_store(p) if flickr
|
231
|
+
|
232
|
+
return Flickr::Token.new(token,perms,p)
|
233
|
+
end
|
234
|
+
|
235
|
+
def to_xml
|
236
|
+
return "<auth><token>#{self.token}</token>"+
|
237
|
+
"<perms>#{self.perms}</perms>"+
|
238
|
+
"<user nsid=\"#{self.user.nsid}\" "+
|
239
|
+
"username=\"#{self.user.username}\" "+
|
240
|
+
"fullname=\"#{self.user.realname}\" /></auth>"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
class Flickr::Blog
|
245
|
+
attr_reader :id, :name, :needspassword, :url
|
246
|
+
|
247
|
+
def initialize(id,name,needspassword,url)
|
248
|
+
@id = id
|
249
|
+
@name = name
|
250
|
+
@needspassword = needspassword
|
251
|
+
@url = url
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
class Flickr::Person
|
256
|
+
attr_accessor :nsid, :username, :realname, :mbox_sha1sum, :location,
|
257
|
+
:photosurl, :profileurl, :photos_firstdate, :photos_firstdatetaken,
|
258
|
+
:photos_count, :info_fetched, :isadmin, :ispro, :iconserver,
|
259
|
+
:bandwidth_max, :bandwidth_used, :filesize_max, :upload_fetched,
|
260
|
+
:friend, :family, :ignored
|
261
|
+
|
262
|
+
def initialize(flickr, nsid, username)
|
263
|
+
@flickr = flickr
|
264
|
+
@nsid = nsid
|
265
|
+
@username = username
|
266
|
+
@info_fetched = false
|
267
|
+
@upload_fetched = false
|
268
|
+
end
|
269
|
+
|
270
|
+
def full_info()
|
271
|
+
self.info_fetched ? self : @flickr.people.getInfo(self)
|
272
|
+
end
|
273
|
+
def upload_status() self.upload_fetched ? self :
|
274
|
+
@flickr.people.getUploadStatus(self) end
|
275
|
+
|
276
|
+
|
277
|
+
# I think this will define a class method. You can't use
|
278
|
+
# Flickr::Person.from_xml and if you just say Person.from_xml, it
|
279
|
+
# can't resolve Flickr::Person::Person
|
280
|
+
def self.from_xml(xml,flickr=nil)
|
281
|
+
els = xml.elements
|
282
|
+
att = xml.root.attributes
|
283
|
+
|
284
|
+
nsid = cond_attr(att,'nsid')
|
285
|
+
username = cond_text(els,'/person/username')
|
286
|
+
|
287
|
+
p = flickr.person_cache_lookup(nsid) if flickr
|
288
|
+
p ||= Flickr::Person.new(flickr,nsid,username)
|
289
|
+
|
290
|
+
p.username = username
|
291
|
+
p.isadmin = cond_attr(att,'isadmin') &&
|
292
|
+
cond_attr(att,'isadmin') == '1'
|
293
|
+
p.ispro = cond_attr(att,'ispro') &&
|
294
|
+
cond_attr(att,'ispro') == '1'
|
295
|
+
p.iconserver = cond_attr(att,'iconserver') &&
|
296
|
+
cond_attr(att,'iconserver').to_i
|
297
|
+
p.realname = cond_text(els,'/person/realname')
|
298
|
+
p.mbox_sha1sum = cond_text(els,'/person/mbox_sha1sum')
|
299
|
+
p.location = cond_text(els,'/person/location')
|
300
|
+
p.photosurl = cond_text(els,'/person/photosurl')
|
301
|
+
p.profileurl = cond_text(els,'/person/profileurl')
|
302
|
+
tstr = cond_text(els,'/person/photos/firstdate')
|
303
|
+
p.photos_firstdate = Time.at(tstr.to_i) if tstr
|
304
|
+
tstr = cond_text(els, '/person/photos/firstdatetaken')
|
305
|
+
p.photos_firstdatetaken = Time.gm(*ParseDate.parsedate(tstr)) if
|
306
|
+
tstr
|
307
|
+
p.photos_count = cond_text(els,'/person/photos/count')
|
308
|
+
p.photos_count = p.photos_count if p.photos_count
|
309
|
+
|
310
|
+
p.info_fetched = true if p.photos_count
|
311
|
+
|
312
|
+
if els['/user/bandwidth']
|
313
|
+
att = els['/user/bandwidth'].attributes
|
314
|
+
p.bandwidth_max = cond_attr(att,'max') &&
|
315
|
+
cond_attr(att,'max').to_i
|
316
|
+
p.bandwidth_used = cond_attr(att,'used') &&
|
317
|
+
cond_attr(att,'used').to_i
|
318
|
+
end
|
319
|
+
if els['/user/filesize']
|
320
|
+
att = els['/user/filesize'].attributes
|
321
|
+
p.filesize_max = cond_attr(att,'max') &&
|
322
|
+
cond_attr(att,'max').to_i
|
323
|
+
end
|
324
|
+
|
325
|
+
p.upload_fetched = true if p.bandwidth_max
|
326
|
+
|
327
|
+
flickr.person_cache_store(p) if flickr
|
328
|
+
return p
|
329
|
+
end
|
330
|
+
|
331
|
+
private
|
332
|
+
def self.cond_text(elements,index)
|
333
|
+
elements[index] ? elements[index].text : nil
|
334
|
+
end
|
335
|
+
|
336
|
+
def self.cond_attr(attributes,name) attributes[name] end
|
337
|
+
end
|
338
|
+
|
339
|
+
class Flickr::Size
|
340
|
+
attr_reader :label,:width,:height,:source,:url
|
341
|
+
|
342
|
+
def initialize(label,width,height,source,url)
|
343
|
+
@label = label
|
344
|
+
@width = width
|
345
|
+
@height = height
|
346
|
+
@source = source
|
347
|
+
@url = url
|
348
|
+
end
|
349
|
+
|
350
|
+
def self.from_xml(xml)
|
351
|
+
att = xml.attributes
|
352
|
+
return Flickr::Size.new(att['label'],att['width'].to_i,
|
353
|
+
att['height'].to_i,att['source'],att['url'])
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
class Flickr::Photo
|
358
|
+
attr_accessor :id, :owner_id, :secret, :server, :title, :ispublic,
|
359
|
+
:isfriend, :isfamily, :ownername, :dateadded,
|
360
|
+
:license_id, :description, :dates, :taken,
|
361
|
+
:lastupdate, :takengranularity, :cancomment, :canaddmeta,
|
362
|
+
:comments, :rotation, :notes, :urls, :permaddmeta,
|
363
|
+
:permcomment, :originalformat
|
364
|
+
|
365
|
+
attr_reader :flickr
|
366
|
+
|
367
|
+
def owner() @owner ||= @flickr.people.getInfo(owner_id) end
|
368
|
+
def sizes() @sizes || @flickr.photos.getSizes(self).sizes end
|
369
|
+
def sizes=(sizes) @sizes = sizes end
|
370
|
+
|
371
|
+
def max_size
|
372
|
+
sizes[:Original] || sizes[:Large] || sizes[:Medium] ||
|
373
|
+
sizes[:Small]
|
374
|
+
end
|
375
|
+
|
376
|
+
def initialize(flickr,id)
|
377
|
+
@flickr = flickr
|
378
|
+
@id = id
|
379
|
+
end
|
380
|
+
|
381
|
+
def exif() @exif ||= @flickr.photos.getExif(self) end
|
382
|
+
def exif=(set) @exif = set end
|
383
|
+
|
384
|
+
def tags() @tags ||= @flickr.tags.getListPhoto(self) end
|
385
|
+
def tags=(set) @tags = set end
|
386
|
+
|
387
|
+
def license() @flickr.photos.licenses.getInfo[@license_id] end
|
388
|
+
|
389
|
+
def contexts() @contexts ||= @flickr.photos.getAllContexts(self) end
|
390
|
+
|
391
|
+
def url(size=nil)
|
392
|
+
base = 'http://static.flickr.com'
|
393
|
+
ext = (size == 'o') ? self.originalformat : 'jpg'
|
394
|
+
return size ?
|
395
|
+
"#{base}/#@server/#{@id}_#{@secret}_#{size}.#{ext}" :
|
396
|
+
"#{base}/#@server/#{@id}_#{@secret}.jpg"
|
397
|
+
end
|
398
|
+
|
399
|
+
def delete() @flickr.photos.delete(self) end
|
400
|
+
|
401
|
+
def self.from_xml(xml,flickr=nil)
|
402
|
+
att = xml.attributes
|
403
|
+
phid = att['id']
|
404
|
+
|
405
|
+
photo = flickr.photo_cache_lookup(phid) if flickr
|
406
|
+
photo ||= Flickr::Photo.new(flickr,phid)
|
407
|
+
|
408
|
+
photo.owner_id ||= att['owner'] || (xml.elements['owner'] &&
|
409
|
+
xml.elements['owner'].attributes['nsid'])
|
410
|
+
photo.secret = att['secret'] if att['secret']
|
411
|
+
photo.originalformat = att['originalformat'] if
|
412
|
+
att['originalformat']
|
413
|
+
photo.server = att['server'].to_i if att['server']
|
414
|
+
photo.title = att['title'] || cond_text(xml.elements,'title')
|
415
|
+
photo.license_id = att['license']
|
416
|
+
photo.rotation = att['rotation'].to_i if att['rotation']
|
417
|
+
|
418
|
+
photo.ispublic = (att['ispublic'].to_i == 1) if att['ispublic']
|
419
|
+
photo.isfriend = (att['isfriend'].to_i == 1) if att['isfriend']
|
420
|
+
photo.isfamily = (att['isfamily'].to_i == 1) if att['isfamily']
|
421
|
+
photo.ownername = att['ownername'] || (xml.elements['owner'] &&
|
422
|
+
xml.elements['owner'].attributes['username'])
|
423
|
+
photo.description = cond_text(xml.elements,'description')
|
424
|
+
photo.dateadded = Time.at(att['dateadded'].to_i) if
|
425
|
+
att['dateadded']
|
426
|
+
if xml.elements['exif']
|
427
|
+
list = []
|
428
|
+
xml.elements.each('exif') do |el|
|
429
|
+
exif = Flickr::Exif.from_xml(el)
|
430
|
+
list << exif
|
431
|
+
end
|
432
|
+
photo.exif = list
|
433
|
+
end
|
434
|
+
if xml.elements['visibility']
|
435
|
+
att = xml.elements['visibility'].attributes
|
436
|
+
photo.ispublic = (att['ispublic'].to_i == 1)
|
437
|
+
photo.isfriend = (att['isfriend'].to_i == 1)
|
438
|
+
photo.isfamily = (att['isfamily'].to_i == 1)
|
439
|
+
end
|
440
|
+
if xml.elements['dates']
|
441
|
+
att = xml.elements['dates'].attributes
|
442
|
+
dates = {}
|
443
|
+
dates[:posted] = Time.at(att['posted'].to_i)
|
444
|
+
dates[:taken] = Time.gm(*ParseDate.parsedate(att['taken']))
|
445
|
+
dates[:lastupdate] = Time.at(att['lastupdate'].to_i)
|
446
|
+
dates[:takengranularity] = att['takengranularity'].to_i
|
447
|
+
photo.dates = dates
|
448
|
+
end
|
449
|
+
if xml.elements['editability']
|
450
|
+
att = xml.elements['editability'].attributes
|
451
|
+
photo.cancomment = (att['cancomment'].to_i == 1)
|
452
|
+
photo.canaddmeta = (att['canaddmeta'].to_i == 1)
|
453
|
+
end
|
454
|
+
photo.comments = cond_text(xml.elements,'comments')
|
455
|
+
photo.comments &&= photo.comments.to_i
|
456
|
+
if xml.elements['notes']
|
457
|
+
notes = []
|
458
|
+
xml.elements['notes'].each_element do |el|
|
459
|
+
notes << Flickr::Note.from_xml(el,photo)
|
460
|
+
end
|
461
|
+
photo.notes = notes
|
462
|
+
end
|
463
|
+
if xml.elements['tags']
|
464
|
+
tags = []
|
465
|
+
xml.elements['tags'].each_element do |el|
|
466
|
+
tags << Flickr::Tag.from_xml(el,photo)
|
467
|
+
end
|
468
|
+
photo.tags = tags
|
469
|
+
end
|
470
|
+
if xml.elements['urls']
|
471
|
+
urls = {}
|
472
|
+
xml.elements['urls'].each_element do |el|
|
473
|
+
att = el.attributes
|
474
|
+
urls[att['type'].intern] = el.text
|
475
|
+
end
|
476
|
+
photo.urls = urls
|
477
|
+
end
|
478
|
+
|
479
|
+
flickr.photo_cache_store(photo) if flickr
|
480
|
+
return photo
|
481
|
+
end
|
482
|
+
|
483
|
+
private
|
484
|
+
def self.cond_text(elements,index)
|
485
|
+
elements[index] ? elements[index].text : nil
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
class Flickr::Exif
|
490
|
+
attr_reader :tagspace,:tagspaceid,:tag,:label
|
491
|
+
attr_accessor :raw,:clean
|
492
|
+
def initialize(tagspace,tagspaceid,tag,label)
|
493
|
+
@tagspace = tagspace
|
494
|
+
@tagspaceid = tagspaceid
|
495
|
+
@tag = tag
|
496
|
+
@label = label
|
497
|
+
end
|
498
|
+
|
499
|
+
def self.from_xml(element)
|
500
|
+
att = element.attributes
|
501
|
+
exif = Flickr::Exif.new(att['tagspace'],att['tagspaceid'].to_i,
|
502
|
+
att['tag'],att['label'])
|
503
|
+
exif.raw=element.elements['raw'].text if element.elements['raw']
|
504
|
+
exif.clean=element.elements['clean'].text if
|
505
|
+
element.elements['clean']
|
506
|
+
return exif
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
class Flickr::PhotoList < Array
|
511
|
+
attr_reader :page,:pages,:perpage,:total
|
512
|
+
|
513
|
+
def initialize(page,pages,perpage,total)
|
514
|
+
@page = page
|
515
|
+
@pages = pages
|
516
|
+
@perpage = perpage
|
517
|
+
@total = total
|
518
|
+
end
|
519
|
+
|
520
|
+
def self.from_xml(xml,flickr=self)
|
521
|
+
att = xml.root.attributes
|
522
|
+
list = Flickr::PhotoList.new(att['page'].to_i,att['pages'].to_i,
|
523
|
+
att['perpage'].to_i,att['total'].to_i)
|
524
|
+
xml.elements['/photos'].each_element do |e|
|
525
|
+
list << Flickr::Photo.from_xml(e,flickr)
|
526
|
+
end
|
527
|
+
return list
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
class Flickr::Category
|
532
|
+
attr_reader :name, :path, :pathids, :groups, :subcats, :id
|
533
|
+
|
534
|
+
def initialize(name,path,pathids)
|
535
|
+
@name = name
|
536
|
+
@path = path
|
537
|
+
@pathids = pathids
|
538
|
+
@groups = []
|
539
|
+
@subcats = []
|
540
|
+
@id = pathids.split('/').last
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
class Flickr::SubCategory
|
545
|
+
attr_reader :name, :id, :count
|
546
|
+
|
547
|
+
def initialize(name,id,count)
|
548
|
+
@name = name
|
549
|
+
@id = id
|
550
|
+
@count = count
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
class Flickr::Group
|
555
|
+
# The privacy attribute is 1 for private groups, 2 for invite-only public
|
556
|
+
# groups and 3 for open public groups.
|
557
|
+
PRIVACY = [nil,:private,:invite,:public]
|
558
|
+
|
559
|
+
attr_accessor :nsid, :name, :members, :online, :chatnsid, :inchat,
|
560
|
+
:description, :privacy, :eighteenplus, :fully_fetched, :admin,
|
561
|
+
:photo_count, :iconserver
|
562
|
+
|
563
|
+
def initialize(flickr,nsid, name=nil, members=nil, online=nil,
|
564
|
+
chatnsid=nil, inchat=nil)
|
565
|
+
@flickr = flickr
|
566
|
+
@nsid = nsid
|
567
|
+
@name = name
|
568
|
+
@members = members
|
569
|
+
@online = online
|
570
|
+
@chatnsid = chatnsid
|
571
|
+
@inchat = inchat
|
572
|
+
@fully_fetched = false
|
573
|
+
end
|
574
|
+
|
575
|
+
def full_info
|
576
|
+
self.fully_fetched ? self : @flickr.groups.getInfo(self)
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
class Flickr::GroupList < Array
|
581
|
+
attr_reader :page,:pages,:perpage,:total
|
582
|
+
|
583
|
+
def initialize(page,pages,perpage,total)
|
584
|
+
@page = page
|
585
|
+
@pages = pages
|
586
|
+
@perpage = perpage
|
587
|
+
@total = total
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
class Flickr::Context
|
592
|
+
attr_reader :prev_id,:prev_secret,:prev_title,:prev_url,
|
593
|
+
:next_id,:next_secret,:next_title,:next_url
|
594
|
+
|
595
|
+
def initialize(prev_id,prev_secret,prev_title,prev_url,
|
596
|
+
next_id,next_secret,next_title,next_url)
|
597
|
+
@prev_id = prev_id
|
598
|
+
@prev_secret = prev_secret
|
599
|
+
@prev_title = prev_title
|
600
|
+
@prev_url = prev_url
|
601
|
+
@next_id = next_id
|
602
|
+
@next_secret = next_secret
|
603
|
+
@next_title = next_title
|
604
|
+
@next_url = next_url
|
605
|
+
end
|
606
|
+
|
607
|
+
def self.from_xml(xml)
|
608
|
+
a0 = xml.elements['prevphoto'].attributes
|
609
|
+
a1 = xml.elements['nextphoto'].attributes
|
610
|
+
return Flickr::Context.new(
|
611
|
+
a0['id'],a0['secret'],a0['title'],a0['url'],
|
612
|
+
a1['id'],a1['secret'],a1['title'],a1['url'])
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
class Flickr::License
|
617
|
+
attr_reader :id, :name, :url
|
618
|
+
def initialize(id,name,url)
|
619
|
+
@id = id
|
620
|
+
@name = name
|
621
|
+
@url = url
|
622
|
+
end
|
623
|
+
|
624
|
+
def self.from_xml(xml)
|
625
|
+
att = xml.attributes
|
626
|
+
return Flickr::License.new(att['id'],att['name'],
|
627
|
+
att['url'])
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
class Flickr::Note
|
632
|
+
attr_accessor :photo, :x, :y, :w, :h, :text, :id, :author_id
|
633
|
+
def initialize(x, y, w, h, text, flickr = nil)
|
634
|
+
@x = x
|
635
|
+
@y = y
|
636
|
+
@w = w
|
637
|
+
@h = h
|
638
|
+
@text = text
|
639
|
+
@flickr = flickr
|
640
|
+
end
|
641
|
+
|
642
|
+
def author() @author_id && @flickr.people.getInfo(@author_id) end
|
643
|
+
|
644
|
+
def from_xml(xml,photo=nil)
|
645
|
+
att = xml.attributes
|
646
|
+
note = Flickr::Note.new(att['x'].to_i,att['y'].to_i,
|
647
|
+
att['w'].to_i,att['h'].to_i,xml.text,
|
648
|
+
photo && photo.flickr)
|
649
|
+
note.photo = photo
|
650
|
+
note.id = att['id']
|
651
|
+
note.author_id = att['author'] if att['author']
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
class Flickr::Count
|
656
|
+
attr_reader :fromdate, :todate, :count
|
657
|
+
def initialize(count,fromdate,todate)
|
658
|
+
@count = count
|
659
|
+
@fromdate = fromdate
|
660
|
+
@todate = todate
|
661
|
+
end
|
662
|
+
|
663
|
+
def self.from_xml(xml)
|
664
|
+
att = xml.attributes
|
665
|
+
return Flickr::Count.new(att['count'].to_i,
|
666
|
+
Time.at(att['fromdate'].to_i),
|
667
|
+
Time.at(att['todate'].to_i))
|
668
|
+
end
|
669
|
+
end
|
670
|
+
|
671
|
+
class Flickr::Tag
|
672
|
+
attr_reader :id, :author_id, :raw, :clean
|
673
|
+
|
674
|
+
def initialize(flickr, id,author_id,raw,clean)
|
675
|
+
@flickr = flickr
|
676
|
+
@id = id
|
677
|
+
@author_id = author_id
|
678
|
+
@raw = raw
|
679
|
+
@clean = clean
|
680
|
+
end
|
681
|
+
|
682
|
+
def author() @flickr.people.getInfo(@author_id) end
|
683
|
+
|
684
|
+
def self.from_xml(xml,flickr=nil)
|
685
|
+
att = xml.attributes
|
686
|
+
clean = xml.text
|
687
|
+
return Flickr::Tag.new(flickr,att['id'],att['author'],
|
688
|
+
att['raw'], clean)
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
class Flickr::PhotoSet < Array
|
693
|
+
attr_accessor :id, :title, :url, :server, :primary_id,
|
694
|
+
:photo_count, :description, :secret, :owner
|
695
|
+
|
696
|
+
def initialize(id,flickr)
|
697
|
+
@id = id
|
698
|
+
@flickr = flickr
|
699
|
+
end
|
700
|
+
|
701
|
+
def <<(photo,raw=false)
|
702
|
+
raw ? super(photo) : @flickr.photosets.addPhoto(self,photo)
|
703
|
+
return self
|
704
|
+
end
|
705
|
+
|
706
|
+
def fetch(extras=nil)
|
707
|
+
return self if @fetched
|
708
|
+
set = @flickr.photosets.getPhotos(self,extras)
|
709
|
+
@fetched = true
|
710
|
+
return set
|
711
|
+
end
|
712
|
+
|
713
|
+
alias photos fetch
|
714
|
+
|
715
|
+
def self.from_xml(xml,flickr=nil)
|
716
|
+
att = xml.attributes
|
717
|
+
psid = att['id']
|
718
|
+
|
719
|
+
set = flickr.photoset_cache_lookup(psid) if flickr
|
720
|
+
set ||= Flickr::PhotoSet.new(psid,flickr)
|
721
|
+
|
722
|
+
set.secret = att['secret']
|
723
|
+
set.owner = att['owner']
|
724
|
+
set.url = att['url']
|
725
|
+
set.server = att['server'].to_i
|
726
|
+
set.primary_id = att['primary'].to_i
|
727
|
+
set.photo_count = att['photos'].to_i
|
728
|
+
set.title = xml.elements['title'].text if xml.elements['title']
|
729
|
+
set.description = xml.elements['description'].text if
|
730
|
+
xml.elements['description']
|
731
|
+
if xml.elements['photo']
|
732
|
+
set.clear
|
733
|
+
xml.elements.each('photo') do |el|
|
734
|
+
set.<<(Flickr::Photo.from_xml(el,flickr),true)
|
735
|
+
end
|
736
|
+
end
|
737
|
+
|
738
|
+
flickr.photoset_cache_store(set) if flickr
|
739
|
+
return set
|
740
|
+
end
|
741
|
+
|
742
|
+
def url
|
743
|
+
owner = @owner || @flickr.photosets.getInfo(self).owner
|
744
|
+
return "http://www.flickr.com/photos/#{owner}/sets/#{@id}"
|
745
|
+
end
|
746
|
+
|
747
|
+
def primary() @primary ||= @flickr.photos.getInfo(@primary_id) end
|
748
|
+
end
|
749
|
+
|
750
|
+
class Flickr::PhotoPool < Array
|
751
|
+
attr_accessor :page, :pages, :perpage, :total, :title, :id
|
752
|
+
|
753
|
+
def initialize(id,flickr)
|
754
|
+
@id = id
|
755
|
+
@flickr = flickr
|
756
|
+
end
|
757
|
+
|
758
|
+
def <<(photo,raw=false)
|
759
|
+
raw ? super(photo) : @flickr.photosets.addPhoto(self,photo)
|
760
|
+
return self
|
761
|
+
end
|
762
|
+
|
763
|
+
def fetch(extras=nil)
|
764
|
+
return self if @fetched
|
765
|
+
pool = @flickr.groups.pools.getPhotos(self,nil,extras,500)
|
766
|
+
@fetched = true
|
767
|
+
return pool
|
768
|
+
end
|
769
|
+
|
770
|
+
def self.from_xml(xml,flickr=nil)
|
771
|
+
att = xml.attributes
|
772
|
+
ppid = att['id']
|
773
|
+
|
774
|
+
pool = flickr.photopool_cache_lookup(ppid)
|
775
|
+
pool ||= Flickr::PhotoPool.new(ppid,flickr)
|
776
|
+
|
777
|
+
pool.page = att['page'].to_i if att['page']
|
778
|
+
pool.pages = att['pages'].to_i if att['pages']
|
779
|
+
pool.perpage = att['perpage'].to_i if att['perpage']
|
780
|
+
pool.total = att['total'].to_i if att['total']
|
781
|
+
if xml.elements['photo']
|
782
|
+
# I'd like to clear the pool, but I can't because I don't know if I'm
|
783
|
+
# parsing the full set or just a single "page".
|
784
|
+
# pool.clear
|
785
|
+
xml.elements.each('photo') do |el|
|
786
|
+
pool.<<(Flickr::Photo.from_xml(el,flickr),true)
|
787
|
+
end
|
788
|
+
end
|
789
|
+
|
790
|
+
flickr.photopool_cache_store(pool) if flickr
|
791
|
+
return pool
|
792
|
+
end
|
793
|
+
end
|