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.
Files changed (4) hide show
  1. data/VERSION.yml +4 -0
  2. data/lib/flickraw.rb +252 -0
  3. data/test/test.rb +67 -0
  4. metadata +65 -0
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 2
3
+ :major: 0
4
+ :minor: 5
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
+