Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 193
- Log:
First stage commit of Typo 4.1, modified for the ROOL site.
Includes all local modifications but a final pass needs to be
made to delete any files left over from earlier Typo versions
that shouldn't be here anymore. See the 'tags' section of the
repository for a clean Typo 4.1 tree.Note that symlinks to shared files in the RISC OS Open theme
directory have been deliberately included this time around; I
decided that on balance it was better to leave them in as
placeholders, since unlike symlinks in app/views/shared, the
Typo theme structure is not a standard Rails concept.
- Author:
- rool
- Date:
- Wed Apr 04 18:51:02 +0100 2007
- Size:
- 15144 Bytes
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> |
6 | # License:: MIT <http://www.opensource.org/licenses/mit-license.php> |
7 | # |
8 | # USAGE: |
9 | # require 'flickr' |
10 | # flickr = Flickr.new # create a flickr client |
11 | # user = flickr.users('sco@scottraymond.net') # lookup a user |
12 | # user.getInfo.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 = recent.first # or very most recent one |
22 | # photo.getInfo.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 | # TODO: |
36 | # - convert dates to ruby Dates |
37 | # - investigate xmlsimple caching |
38 | # - make to_s methods automatic? |
39 | |
40 | # - complete tests |
41 | # - in tests, implement a MockFlickr object that has stored responses. automate the getting of the responses? |
42 | |
43 | # - test on a few platforms |
44 | # - seek feedback from somebody |
45 | # - make a kickass demo, including autocompleting-ajax photo lookup ala http://mir.aculo.us/images/autocomplete1.mov |
46 | |
47 | require 'cgi' |
48 | require 'net/http' |
49 | |
50 | # Flickr client class. Requires an API key, and optionally takes an email and password for authentication |
51 | class Flickr |
52 | |
53 | attr_accessor :user |
54 | |
55 | # Replace this API key with your own (see http://www.flickr.com/services/api/misc.api_keys.html) |
56 | def initialize(api_key=FLICKR_KEY, email=nil, password=nil) |
57 | @api_key = api_key |
58 | @host = 'http://flickr.com' |
59 | @api = '/services/rest' |
60 | login(email, password) if email and password |
61 | end |
62 | |
63 | # Takes a Flickr API method name and set of parameters; returns an XmlSimple object with the response |
64 | def request(method, *params) |
65 | response = XmlSimple.xml_in(http_get(request_url(method, params)), { 'ForceArray' => false }) |
66 | raise response['err']['msg'] if response['stat'] != 'ok' |
67 | response |
68 | end |
69 | |
70 | # Takes a Flickr API method name and set of parameters; returns the correct URL for the REST API. |
71 | # If @email and @password are present, authentication information is included |
72 | def request_url(method, *params) |
73 | url = "#{@host}#{@api}/?api_key=#{@api_key}&method=flickr.#{method}" |
74 | params[0][0].each_key do |key| url += "&#{key}=" + CGI::escape(params[0][0][key].to_s) end if params[0][0] |
75 | url += "&email=#{@email}&password=#{@password}" if @email and @password |
76 | url |
77 | end |
78 | |
79 | # Does an HTTP GET on a given URL and returns the response body |
80 | def http_get(url) |
81 | Net::HTTP.get_response(URI.parse(url)).body.to_s |
82 | end |
83 | |
84 | # Stores authentication credentials to use on all subsequent calls. |
85 | # If authentication succeeds, returns a User object |
86 | def login(email='', password='') |
87 | @email = email |
88 | @password = password |
89 | user = request('test.login')['user'] rescue fail |
90 | @user = User.new(user['id']) |
91 | end |
92 | |
93 | # Implements flickr.urls.lookupGroup and flickr.urls.lookupUser |
94 | def find_by_url(url) |
95 | response = urls_lookupUser('url'=>url) rescue urls_lookupGroup('url'=>url) rescue nil |
96 | (response['user']) ? User.new(response['user']['id']) : Group.new(response['group']['id']) unless response.nil? |
97 | end |
98 | |
99 | # Implements flickr.photos.getRecent and flickr.photos.search |
100 | def photos(*criteria) |
101 | photos = (criteria[0]) ? photos_search(criteria[0]) : photos_getRecent |
102 | photos['photos']['photo'].collect { |photo| Photo.new(photo['id']) } |
103 | end |
104 | |
105 | # Gets public photos with a given tag |
106 | def tag(tag) |
107 | photos('tags'=>tag) |
108 | end |
109 | |
110 | # Implements flickr.people.getOnlineList, flickr.people.findByEmail, and flickr.people.findByUsername |
111 | def users(lookup=nil) |
112 | if(lookup) |
113 | user = people_findByEmail('find_email'=>lookup)['user'] rescue people_findByUsername('username'=>lookup)['user'] |
114 | return User.new(user['nsid']) |
115 | else |
116 | return people_getOnlineList['online']['user'].collect { |person| User.new(person['nsid']) } |
117 | end |
118 | end |
119 | |
120 | # Implements flickr.groups.getActiveList |
121 | def groups |
122 | groups_getActiveList['activegroups']['group'].collect { |group| Group.new(group['nsid']) } |
123 | end |
124 | |
125 | # Implements flickr.tags.getRelated |
126 | def related_tags(tag) |
127 | tags_getRelated('tag_id'=>tag)['tags']['tag'] |
128 | end |
129 | |
130 | # Implements flickr.photos.licenses.getInfo |
131 | def licenses |
132 | photos_licenses_getInfo['licenses']['license'] |
133 | end |
134 | |
135 | # Implements everything else. |
136 | # Any method not defined explicitly will be passed on to the Flickr API, |
137 | # and return an XmlSimple document. For example, Flickr#test_echo is not defined, |
138 | # so it will pass the call to the flickr.test.echo method. |
139 | # e.g., Flickr#test_echo['stat'] should == 'ok' |
140 | def method_missing(method_id, *params) |
141 | request(method_id.id2name.gsub(/_/, '.'), params[0]) |
142 | end |
143 | |
144 | # Todo: |
145 | # logged_in? |
146 | # if logged in: |
147 | # flickr.blogs.getList |
148 | # flickr.favorites.add |
149 | # flickr.favorites.remove |
150 | # flickr.groups.browse |
151 | # flickr.photos.getCounts |
152 | # flickr.photos.getNotInSet |
153 | # flickr.photos.getUntagged |
154 | # flickr.photosets.create |
155 | # flickr.photosets.orderSets |
156 | # flickr.tags.getListUserPopular |
157 | # flickr.test.login |
158 | # uploading |
159 | class User |
160 | |
161 | attr_reader :client, :id, :name, :location, :photos_url, :url, :count, :firstdate, :firstdatetaken |
162 | |
163 | def initialize(id=nil, username=nil, email=nil, password=nil) |
164 | @id = id |
165 | @username = username |
166 | @email = email |
167 | @password = password |
168 | @client = Flickr.new |
169 | @client.login(email, password) if email and password |
170 | end |
171 | |
172 | def username |
173 | @username.nil? ? getInfo.username : @username |
174 | end |
175 | def name |
176 | @name.nil? ? getInfo.name : @name |
177 | end |
178 | def location |
179 | @location.nil? ? getInfo.location : @location |
180 | end |
181 | def count |
182 | @count.nil? ? getInfo.count : @count |
183 | end |
184 | def firstdate |
185 | @firstdate.nil? ? getInfo.firstdate : @firstdate |
186 | end |
187 | def firstdatetaken |
188 | @firstdatetaken.nil? ? getInfo.firstdatetaken : @firstdatetaken |
189 | end |
190 | def photos_url |
191 | @photos_url.nil? ? getInfo.photos_url : @photos_url |
192 | end |
193 | def url |
194 | @url.nil? ? getInfo.url : @url |
195 | end |
196 | |
197 | # Implements flickr.people.getPublicGroups |
198 | def groups |
199 | @client.people_getPublicGroups('user_id'=>@id)['groups']['group'].collect { |group| Group.new(group['nsid']) } |
200 | end |
201 | |
202 | # Implements flickr.people.getPublicPhotos |
203 | def photos |
204 | @client.people_getPublicPhotos('user_id'=>@id)['photos']['photo'].collect { |photo| Photo.new(photo['id']) } |
205 | # what about non-public photos? |
206 | end |
207 | |
208 | # Gets photos with a given tag |
209 | def tag(tag) |
210 | @client.photos('user_id'=>@id, 'tags'=>tag) |
211 | end |
212 | |
213 | # Implements flickr.contacts.getPublicList and flickr.contacts.getList |
214 | def contacts |
215 | @client.contacts_getPublicList('user_id'=>@id)['contacts']['contact'].collect { |contact| User.new(contact['nsid']) } |
216 | #or |
217 | end |
218 | |
219 | # Implements flickr.favorites.getPublicList and flickr.favorites.getList |
220 | def favorites |
221 | @client.favorites_getPublicList('user_id'=>@id)['photos']['photo'].collect { |photo| Photo.new(photo['id']) } |
222 | #or |
223 | end |
224 | |
225 | # Implements flickr.photosets.getList |
226 | def photosets |
227 | @client.photosets_getList('user_id'=>@id)['photosets']['photoset'].collect { |photoset| Photoset.new(photoset['id']) } |
228 | end |
229 | |
230 | # Implements flickr.tags.getListUser |
231 | def tags |
232 | @client.tags_getListUser('user_id'=>@id)['who']['tags']['tag'].collect { |tag| tag } |
233 | end |
234 | |
235 | # Implements flickr.photos.getContactsPublicPhotos and flickr.photos.getContactsPhotos |
236 | def contactsPhotos |
237 | @client.photos_getContactsPublicPhotos('user_id'=>@id)['photos']['photo'].collect { |photo| Photo.new(photo['id']) } |
238 | # or |
239 | #@client.photos_getContactsPhotos['photos']['photo'].collect { |photo| Photo.new(photo['id']) } |
240 | end |
241 | |
242 | def to_s |
243 | @name |
244 | end |
245 | |
246 | private |
247 | |
248 | # Implements flickr.people.getInfo, flickr.urls.getUserPhotos, and flickr.urls.getUserProfile |
249 | def getInfo |
250 | info = @client.people_getInfo('user_id'=>@id)['person'] |
251 | @username = info['username'] |
252 | @name = info['realname'] |
253 | @location = info['location'] |
254 | @count = info['photos']['count'] |
255 | @firstdate = info['photos']['firstdate'] |
256 | @firstdatetaken = info['photos']['firstdatetaken'] |
257 | @photos_url = @client.urls_getUserPhotos('user_id'=>@id)['user']['url'] |
258 | @url = @client.urls_getUserProfile('user_id'=>@id)['user']['url'] |
259 | self |
260 | end |
261 | |
262 | end |
263 | |
264 | class Photo |
265 | |
266 | attr_reader :id, :client |
267 | |
268 | def initialize(id=nil) |
269 | @id = id |
270 | @client = Flickr.new |
271 | end |
272 | |
273 | def title |
274 | @title.nil? ? getInfo.title : @title |
275 | end |
276 | |
277 | def owner |
278 | @owner.nil? ? getInfo.owner : @owner |
279 | end |
280 | |
281 | def server |
282 | @server.nil? ? getInfo.server : @server |
283 | end |
284 | |
285 | def isfavorite |
286 | @isfavorite.nil? ? getInfo.isfavorite : @isfavorite |
287 | end |
288 | |
289 | def license |
290 | @license.nil? ? getInfo.license : @license |
291 | end |
292 | |
293 | def rotation |
294 | @rotation.nil? ? getInfo.rotation : @rotation |
295 | end |
296 | |
297 | def description |
298 | @description.nil? ? getInfo.description : @description |
299 | end |
300 | |
301 | def notes |
302 | @notes.nil? ? getInfo.notes : @notes |
303 | end |
304 | |
305 | # Returns the URL for the photo page (default or any specified size) |
306 | def url(size='Medium') |
307 | if size=='Medium' |
308 | owner.photos_url+id |
309 | else |
310 | sizes(size)['url'] |
311 | end |
312 | end |
313 | |
314 | # Returns the URL for the image (default or any specified size) |
315 | def source(size='Medium') |
316 | sizes(size)['source'] |
317 | end |
318 | |
319 | # Returns the photo file data itself, in any specified size. Example: File.open(photo.title, 'w') { |f| f.puts photo.file } |
320 | def file(size='Medium') |
321 | Net::HTTP.get_response(URI.parse(source(size))).body |
322 | end |
323 | |
324 | # Unique filename for the image, based on the Flickr NSID |
325 | def filename |
326 | "#{@id}.jpg" |
327 | end |
328 | |
329 | # Implements flickr.photos.getContext |
330 | def context |
331 | context = @client.photos_getContext('photo_id'=>@id) |
332 | @previousPhoto = Photo.new(context['prevphoto']['id']) |
333 | @nextPhoto = Photo.new(context['nextphoto']['id']) |
334 | return [@previousPhoto, @nextPhoto] |
335 | end |
336 | |
337 | # Implements flickr.photos.getExif |
338 | def exif |
339 | @client.photos_getExif('photo_id'=>@id)['photo'] |
340 | end |
341 | |
342 | # Implements flickr.photos.getPerms |
343 | def permissions |
344 | @client.photos_getPerms('photo_id'=>@id)['perms'] |
345 | end |
346 | |
347 | # Implements flickr.photos.getSizes |
348 | def sizes(size=nil) |
349 | sizes = @client.photos_getSizes('photo_id'=>@id)['sizes']['size'] |
350 | sizes = sizes.find{|asize| asize['label']==size} if size |
351 | return sizes |
352 | end |
353 | |
354 | # flickr.tags.getListPhoto |
355 | def tags |
356 | @client.tags_getListPhoto('photo_id'=>@id)['photo']['tags'] |
357 | end |
358 | |
359 | # Implements flickr.photos.notes.add |
360 | def add_note(note) |
361 | end |
362 | |
363 | # Implements flickr.photos.setDates |
364 | def dates=(dates) |
365 | end |
366 | |
367 | # Implements flickr.photos.setPerms |
368 | def perms=(perms) |
369 | end |
370 | |
371 | # Implements flickr.photos.setTags |
372 | def tags=(tags) |
373 | end |
374 | |
375 | # Implements flickr.photos.setMeta |
376 | def title=(title) |
377 | end |
378 | def description=(title) |
379 | end |
380 | |
381 | # Implements flickr.photos.addTags |
382 | def add_tag(tag) |
383 | end |
384 | |
385 | # Implements flickr.photos.removeTag |
386 | def remove_tag(tag) |
387 | end |
388 | |
389 | # Implements flickr.photos.transform.rotate |
390 | def rotate |
391 | end |
392 | |
393 | # Implements flickr.blogs.postPhoto |
394 | def postToBlog(blog_id, title='', description='') |
395 | @client.blogs_postPhoto('photo_id'=>@id, 'title'=>title, 'description'=>description) |
396 | end |
397 | |
398 | # Implements flickr.photos.notes.delete |
399 | def deleteNote(note_id) |
400 | end |
401 | |
402 | # Implements flickr.photos.notes.edit |
403 | def editNote(note_id) |
404 | end |
405 | |
406 | # Converts the Photo to a string by returning its title |
407 | def to_s |
408 | getInfo.title |
409 | end |
410 | |
411 | private |
412 | |
413 | # Implements flickr.photos.getInfo |
414 | def getInfo |
415 | info = @client.photos_getInfo('photo_id'=>@id)['photo'] |
416 | @title = info['title'] |
417 | @owner = User.new(info['owner']['nsid']) |
418 | @server = info['server'] |
419 | @isfavorite = info['isfavorite'] |
420 | @license = info['license'] |
421 | @rotation = info['rotation'] |
422 | @description = info['description'] |
423 | @notes = info['notes']['note']#.collect { |note| Note.new(note.id) } |
424 | self |
425 | end |
426 | |
427 | end |
428 | |
429 | # Todo: |
430 | # flickr.groups.pools.add |
431 | # flickr.groups.pools.getContext |
432 | # flickr.groups.pools.getGroups |
433 | # flickr.groups.pools.getPhotos |
434 | # flickr.groups.pools.remove |
435 | class Group |
436 | attr_reader :id, :client, :name, :members, :online, :privacy, :chatid, :chatcount, :url |
437 | |
438 | def initialize(id=nil) |
439 | @id = id |
440 | @client = Flickr.new |
441 | end |
442 | |
443 | # Implements flickr.groups.getInfo and flickr.urls.getGroup |
444 | # private, once we can call it as needed |
445 | def getInfo |
446 | info = @client.groups_getInfo('group_id'=>@id)['group'] |
447 | @name = info['name'] |
448 | @members = info['members'] |
449 | @online = info['online'] |
450 | @privacy = info['privacy'] |
451 | @chatid = info['chatid'] |
452 | @chatcount = info['chatcount'] |
453 | @url = @client.urls_getGroup('group_id'=>@id)['group']['url'] |
454 | self |
455 | end |
456 | |
457 | end |
458 | |
459 | # Todo: |
460 | # flickr.photosets.delete |
461 | # flickr.photosets.editMeta |
462 | # flickr.photosets.editPhotos |
463 | # flickr.photosets.getContext |
464 | # flickr.photosets.getInfo |
465 | # flickr.photosets.getPhotos |
466 | class Photoset |
467 | |
468 | attr_reader :id, :client, :owner, :primary, :photos, :title, :description, :url |
469 | |
470 | def initialize(id=nil) |
471 | @id = id |
472 | @client = Flickr.new |
473 | end |
474 | |
475 | # Implements flickr.photosets.getInfo |
476 | # private, once we can call it as needed |
477 | def getInfo |
478 | info = @client.photosets_getInfo('photosets_id'=>@id)['photoset'] |
479 | @owner = User.new(info['owner']) |
480 | @primary = info['primary'] |
481 | @photos = info['photos'] |
482 | @title = info['title'] |
483 | @description = info['description'] |
484 | @url = "http://www.flickr.com/photos/#{@owner.getInfo.username}/sets/#{@id}/" |
485 | self |
486 | end |
487 | |
488 | end |
489 | |
490 | end |