wideopenspaces-flickraw 0.5.2
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/VERSION.yml +4 -0
- data/lib/flickraw.rb +252 -0
- data/test/test.rb +67 -0
- metadata +65 -0
data/VERSION.yml
ADDED
data/lib/flickraw.rb
ADDED
@@ -0,0 +1,252 @@
|
|
1
|
+
# Copyright (c) 2006 Mael Clerambault <maelclerambault@yahoo.fr>
|
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 included in all
|
12
|
+
# 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 NONINFRINGEMENT.
|
17
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
|
23
|
+
require 'net/http'
|
24
|
+
require 'md5'
|
25
|
+
require 'json'
|
26
|
+
require 'cgi'
|
27
|
+
|
28
|
+
module FlickRaw
|
29
|
+
VERSION='0.5.2'
|
30
|
+
|
31
|
+
FLICKR_HOST='api.flickr.com'.freeze
|
32
|
+
|
33
|
+
# Path of flickr REST API
|
34
|
+
REST_PATH='/services/rest/?'.freeze
|
35
|
+
|
36
|
+
# Path of flickr auth page
|
37
|
+
AUTH_PATH='/services/auth/?'.freeze
|
38
|
+
|
39
|
+
# Path of flickr upload
|
40
|
+
UPLOAD_PATH='/services/upload/'.freeze
|
41
|
+
|
42
|
+
@api_key = '7b124df89b638e545e3165293883ef62'
|
43
|
+
|
44
|
+
module SimpleOStruct # :nodoc:
|
45
|
+
def __attr_define(k,v)
|
46
|
+
instance_variable_set "@#{k}", v
|
47
|
+
meta = class << self; self; end
|
48
|
+
meta.class_eval { attr_reader k.to_s }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Response # :nodoc:
|
53
|
+
include SimpleOStruct
|
54
|
+
def initialize(h)
|
55
|
+
h.each {|k, v| __attr_define k, Response.structify(v, k) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.structify(obj, name = '')
|
59
|
+
if obj.is_a? Hash
|
60
|
+
if name =~ /s$/ and obj[$`].is_a? Array
|
61
|
+
list = structify obj.delete($`)
|
62
|
+
list.extend SimpleOStruct
|
63
|
+
list.instance_eval { obj.each {|kv, vv|
|
64
|
+
__attr_define kv, vv
|
65
|
+
if kv == "id"
|
66
|
+
__attr_define "fid", vv
|
67
|
+
end
|
68
|
+
} }
|
69
|
+
list
|
70
|
+
elsif obj.keys == ['_content']
|
71
|
+
obj['_content'].to_s
|
72
|
+
else
|
73
|
+
Response.new obj
|
74
|
+
end
|
75
|
+
elsif obj.is_a? Array
|
76
|
+
obj.collect {|e| structify e}
|
77
|
+
else
|
78
|
+
obj
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_s; @_content || super end
|
83
|
+
end
|
84
|
+
|
85
|
+
class FailedResponse < StandardError
|
86
|
+
attr_reader :code
|
87
|
+
alias :msg :message
|
88
|
+
def initialize(msg, code, req)
|
89
|
+
@code = code
|
90
|
+
super("'#{req}' - #{msg}")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Request
|
95
|
+
def initialize(flickr = nil) # :nodoc:
|
96
|
+
@flickr = flickr
|
97
|
+
|
98
|
+
self.class.flickr_objects.each {|name|
|
99
|
+
klass = self.class.const_get name.capitalize
|
100
|
+
instance_variable_set "@#{name}", klass.new(@flickr)
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.build_request(req) # :nodoc:
|
105
|
+
method_nesting = req.split '.'
|
106
|
+
raise "'#{@name}' : Method name mismatch" if method_nesting.shift != request_name.split('.').last
|
107
|
+
|
108
|
+
if method_nesting.size > 1
|
109
|
+
name = method_nesting.first
|
110
|
+
class_name = name.capitalize
|
111
|
+
if const_defined? class_name
|
112
|
+
klass = const_get( class_name)
|
113
|
+
else
|
114
|
+
klass = Class.new Request
|
115
|
+
const_set(class_name, klass)
|
116
|
+
attr_reader name
|
117
|
+
flickr_objects << name
|
118
|
+
end
|
119
|
+
|
120
|
+
klass.build_request method_nesting.join('.')
|
121
|
+
else
|
122
|
+
req = method_nesting.first
|
123
|
+
define_method(req) { |*args|
|
124
|
+
class_req = self.class.request_name
|
125
|
+
@flickr.call class_req + '.' + req, *args
|
126
|
+
}
|
127
|
+
flickr_methods << req
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# List of the flickr subobjects of this object
|
132
|
+
def self.flickr_objects; @flickr_objects ||= [] end
|
133
|
+
|
134
|
+
# List of the flickr methods of this object
|
135
|
+
def self.flickr_methods; @flickr_methods ||= [] end
|
136
|
+
|
137
|
+
# Returns the prefix of the request corresponding to this class.
|
138
|
+
def self.request_name; name.downcase.gsub(/::/, '.').sub(/[^\.]+\./, '') end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Root class of the flickr api hierarchy.
|
142
|
+
class Flickr < Request
|
143
|
+
def initialize # :nodoc:
|
144
|
+
super self
|
145
|
+
@token = nil
|
146
|
+
end
|
147
|
+
|
148
|
+
# This is the central method. It does the actual request to the flickr server.
|
149
|
+
#
|
150
|
+
# Raises FailedResponse if the response status is _failed_.
|
151
|
+
def call(req, args={})
|
152
|
+
path = REST_PATH + build_args(args, req).collect { |a, v| "#{a}=#{v}" }.join('&')
|
153
|
+
http_response = Net::HTTP.start(FLICKR_HOST) { |http| http.get(path, 'User-Agent' => "Flickraw/#{VERSION}") }
|
154
|
+
parse_response(http_response, req)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Use this to upload the photo in _file_.
|
158
|
+
#
|
159
|
+
# flickr.upload_photo '/path/to/the/photo', :title => 'Title', :description => 'This is the description'
|
160
|
+
#
|
161
|
+
# See http://www.flickr.com/services/api/upload.api.html for more information on the arguments.
|
162
|
+
def upload_photo(file, args={})
|
163
|
+
photo = File.open(file, 'rb') { |f| f.read }
|
164
|
+
boundary = MD5.md5(photo).to_s
|
165
|
+
|
166
|
+
header = {'Content-type' => "multipart/form-data, boundary=#{boundary} ", 'User-Agent' => "Flickraw/#{VERSION}"}
|
167
|
+
query = ''
|
168
|
+
build_args(args).each { |a, v|
|
169
|
+
query <<
|
170
|
+
"--#{boundary}\r\n" <<
|
171
|
+
"Content-Disposition: form-data; name=\"#{a}\"\r\n\r\n" <<
|
172
|
+
"#{v}\r\n"
|
173
|
+
}
|
174
|
+
query <<
|
175
|
+
"--#{boundary}\r\n" <<
|
176
|
+
"Content-Disposition: form-data; name=\"photo\"; filename=\"#{file}\"\r\n" <<
|
177
|
+
"Content-Transfer-Encoding: binary\r\n" <<
|
178
|
+
"Content-Type: image/jpeg\r\n\r\n" <<
|
179
|
+
photo <<
|
180
|
+
"\r\n" <<
|
181
|
+
"--#{boundary}--"
|
182
|
+
|
183
|
+
http_response = Net::HTTP.start(FLICKR_HOST) { |http| http.post(UPLOAD_PATH, query, header) }
|
184
|
+
xml = http_response.body
|
185
|
+
if xml[/stat="(\w+)"/, 1] == 'fail'
|
186
|
+
msg = xml[/msg="([^"]+)"/, 1]
|
187
|
+
code = xml[/code="([^"]+)"/, 1]
|
188
|
+
raise FailedResponse.new(msg, code, 'flickr.upload')
|
189
|
+
end
|
190
|
+
Response.structify( {:stat => 'ok', :photoid => xml[/<photoid>(\w+)<\/photoid>/, 1], :ticketid => xml[/<ticketid>([^<]+)<\/ticketid>/, 1]})
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
def parse_response(response, req = nil)
|
195
|
+
json = JSON.load(response.body)
|
196
|
+
raise FailedResponse.new(json['message'], json['code'], req) if json.delete('stat') == 'fail'
|
197
|
+
name, json = json.to_a.first if json.size == 1
|
198
|
+
|
199
|
+
res = Response.structify json, name
|
200
|
+
lookup_token(req, res)
|
201
|
+
res
|
202
|
+
end
|
203
|
+
|
204
|
+
def build_args(args={}, req = nil)
|
205
|
+
full_args = {:api_key => FlickRaw.api_key, :format => 'json', :nojsoncallback => 1}
|
206
|
+
full_args[:method] = req if req
|
207
|
+
full_args[:auth_token] = @token if @token
|
208
|
+
args.each {|k, v| full_args[k.to_sym] = v.to_s }
|
209
|
+
full_args[:api_sig] = FlickRaw.api_sig(full_args) if FlickRaw.shared_secret
|
210
|
+
args.each {|k, v| full_args[k.to_sym] = CGI.escape(v.to_s) } if req
|
211
|
+
full_args
|
212
|
+
end
|
213
|
+
|
214
|
+
def lookup_token(req, res)
|
215
|
+
token_reqs = ['flickr.auth.getToken', 'flickr.auth.getFullToken', 'flickr.auth.checkToken']
|
216
|
+
@token = res.token if token_reqs.include?(req) and res.respond_to?(:token)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
class << self
|
221
|
+
# Your flickr API key, see http://www.flickr.com/services/api/keys for more information
|
222
|
+
attr_accessor :api_key
|
223
|
+
|
224
|
+
# The shared secret of _api_key_, see http://www.flickr.com/services/api/keys for more information
|
225
|
+
attr_accessor :shared_secret
|
226
|
+
|
227
|
+
# Returns the flickr auth URL.
|
228
|
+
def auth_url(args={})
|
229
|
+
full_args = {:api_key => FlickRaw.api_key, :perms => 'read'}
|
230
|
+
args.each {|k, v| full_args[k.to_sym] = v }
|
231
|
+
full_args[:api_sig] = api_sig(full_args) if FlickRaw.shared_secret
|
232
|
+
|
233
|
+
'http://' + FLICKR_HOST + AUTH_PATH + full_args.collect { |a, v| "#{a}=#{v}" }.join('&')
|
234
|
+
end
|
235
|
+
|
236
|
+
# Returns the signature of hsh. This is meant to be passed in the _api_sig_ parameter.
|
237
|
+
def api_sig(hsh)
|
238
|
+
MD5.md5(FlickRaw.shared_secret + hsh.sort{|a, b| a[0].to_s <=> b[0].to_s }.flatten.join).to_s
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
methods = Flickr.new.call 'flickr.reflection.getMethods'
|
243
|
+
methods.each { |method| Flickr.build_request method }
|
244
|
+
end
|
245
|
+
|
246
|
+
# Use this to access the flickr API easily. You can type directly the flickr requests as they are described on the flickr website.
|
247
|
+
# require 'flickraw'
|
248
|
+
#
|
249
|
+
# recent_photos = flickr.photos.getRecent
|
250
|
+
# puts recent_photos[0].title
|
251
|
+
def flickr; $flickraw ||= FlickRaw::Flickr.new end
|
252
|
+
|
data/test/test.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'lib/flickraw'
|
3
|
+
|
4
|
+
# utf8 hack
|
5
|
+
def u(str)
|
6
|
+
str.gsub(/\\+u([0-9a-fA-F]{4,4})/u){["#$1".hex ].pack('U*')}
|
7
|
+
end
|
8
|
+
|
9
|
+
class Basic < Test::Unit::TestCase
|
10
|
+
def test_known
|
11
|
+
known_methods = ["flickr.activity.userComments", "flickr.activity.userPhotos", "flickr.auth.checkToken", "flickr.auth.getFrob", "flickr.auth.getFullToken", "flickr.auth.getToken", "flickr.blogs.getList", "flickr.blogs.postPhoto", "flickr.contacts.getList", "flickr.contacts.getPublicList", "flickr.favorites.add", "flickr.favorites.getList", "flickr.favorites.getPublicList", "flickr.favorites.remove", "flickr.groups.browse", "flickr.groups.getInfo", "flickr.groups.pools.add", "flickr.groups.pools.getContext", "flickr.groups.pools.getGroups", "flickr.groups.pools.getPhotos", "flickr.groups.pools.remove", "flickr.groups.search", "flickr.interestingness.getList", "flickr.people.findByEmail", "flickr.people.findByUsername", "flickr.people.getInfo", "flickr.people.getPublicGroups", "flickr.people.getPublicPhotos", "flickr.people.getUploadStatus", "flickr.photos.addTags", "flickr.photos.comments.addComment", "flickr.photos.comments.deleteComment", "flickr.photos.comments.editComment", "flickr.photos.comments.getList", "flickr.photos.delete", "flickr.photos.geo.getLocation", "flickr.photos.geo.getPerms", "flickr.photos.geo.removeLocation", "flickr.photos.geo.setLocation", "flickr.photos.geo.setPerms", "flickr.photos.getAllContexts", "flickr.photos.getContactsPhotos", "flickr.photos.getContactsPublicPhotos", "flickr.photos.getContext", "flickr.photos.getCounts", "flickr.photos.getExif", "flickr.photos.getFavorites", "flickr.photos.getInfo", "flickr.photos.getNotInSet", "flickr.photos.getPerms", "flickr.photos.getRecent", "flickr.photos.getSizes", "flickr.photos.getUntagged", "flickr.photos.getWithGeoData", "flickr.photos.getWithoutGeoData", "flickr.photos.licenses.getInfo", "flickr.photos.licenses.setLicense", "flickr.photos.notes.add", "flickr.photos.notes.delete", "flickr.photos.notes.edit", "flickr.photos.recentlyUpdated", "flickr.photos.removeTag", "flickr.photos.search", "flickr.photos.setDates", "flickr.photos.setMeta", "flickr.photos.setPerms", "flickr.photos.setTags", "flickr.photos.transform.rotate", "flickr.photos.upload.checkTickets", "flickr.photosets.addPhoto", "flickr.photosets.comments.addComment", "flickr.photosets.comments.deleteComment", "flickr.photosets.comments.editComment", "flickr.photosets.comments.getList", "flickr.photosets.create", "flickr.photosets.delete", "flickr.photosets.editMeta", "flickr.photosets.editPhotos", "flickr.photosets.getContext", "flickr.photosets.getInfo", "flickr.photosets.getList", "flickr.photosets.getPhotos", "flickr.photosets.orderSets", "flickr.photosets.removePhoto", "flickr.reflection.getMethodInfo", "flickr.reflection.getMethods", "flickr.tags.getHotList", "flickr.tags.getListPhoto", "flickr.tags.getListUser", "flickr.tags.getListUserPopular", "flickr.tags.getListUserRaw", "flickr.tags.getRelated", "flickr.test.echo", "flickr.test.login", "flickr.test.null", "flickr.urls.getGroup", "flickr.urls.getUserPhotos", "flickr.urls.getUserProfile", "flickr.urls.lookupGroup", "flickr.urls.lookupUser"]
|
12
|
+
found_methods = flickr.reflection.getMethods
|
13
|
+
assert_instance_of Array, found_methods
|
14
|
+
known_methods.each { |m| assert found_methods.include?(m), m}
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_found
|
18
|
+
found_methods = flickr.reflection.getMethods
|
19
|
+
found_methods.each { |m|
|
20
|
+
assert_nothing_raised {
|
21
|
+
begin
|
22
|
+
eval m
|
23
|
+
rescue FlickRaw::FailedResponse
|
24
|
+
end
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_photos
|
30
|
+
list = flickr.photos.getRecent :per_page => '10'
|
31
|
+
assert_instance_of Array, list
|
32
|
+
assert_equal(list.size, 10)
|
33
|
+
|
34
|
+
id = secret = info = nil
|
35
|
+
assert_nothing_raised(NoMethodError) {
|
36
|
+
id = list[0].id
|
37
|
+
secret = list[0].secret
|
38
|
+
}
|
39
|
+
assert_nothing_raised(FlickRaw::FailedResponse) {
|
40
|
+
info = flickr.photos.getInfo :photo_id => id, :secret => secret
|
41
|
+
}
|
42
|
+
assert_respond_to info, :id
|
43
|
+
assert_respond_to info, :secret
|
44
|
+
assert_respond_to info, :title
|
45
|
+
assert_respond_to info, :description
|
46
|
+
assert_respond_to info, :owner
|
47
|
+
assert_respond_to info, :dates
|
48
|
+
assert_respond_to info, :comments
|
49
|
+
assert_respond_to info, :tags
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_url_escape
|
53
|
+
result_set = nil
|
54
|
+
assert_nothing_raised {
|
55
|
+
result_set = flickr.photos.search :text => "family vacation"
|
56
|
+
}
|
57
|
+
assert_operator result_set.total.to_i, :>=, 0
|
58
|
+
|
59
|
+
# Unicode tests
|
60
|
+
echo = nil
|
61
|
+
utf8_text = "Hélène François, €uro"
|
62
|
+
assert_nothing_raised {
|
63
|
+
echo = flickr.test.echo :utf8_text => utf8_text
|
64
|
+
}
|
65
|
+
assert_equal u(echo.utf8_text), utf8_text
|
66
|
+
end
|
67
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wideopenspaces-flickraw
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mael Clerambault
|
8
|
+
- Jacob Stetser
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-01-21 00:00:00 -08:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: json
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: Flickr library with a syntax close to the syntax described on http://www.flickr.com/services/api
|
26
|
+
email: jake@wideopenspac.es
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- VERSION.yml
|
35
|
+
- lib/flickraw.rb
|
36
|
+
- test/test.rb
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://github.com/wideopenspaces/flickraw
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- --inline-source
|
42
|
+
- --charset=UTF-8
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.2.0
|
61
|
+
signing_key:
|
62
|
+
specification_version: 2
|
63
|
+
summary: Flickr library with a syntax close to the syntax described on http://www.flickr.com/services/api
|
64
|
+
test_files: []
|
65
|
+
|