genki-flickraw 0.8.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
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.
@@ -0,0 +1,161 @@
1
+ = Flickraw
2
+
3
+ Flickraw is a library to access flickr[http://flickr.com] api in a simple way.
4
+ It maps exactly the methods described in {the official api documentation}[http://www.flickr.com/services/api].
5
+ It also tries to present the data returned in a simple and intuitive way.
6
+ The methods are fetched from flickr when loading the library by using introspection capabilities. So it is always up-to-date with regards to new methods added by flickr.
7
+
8
+ The github repository: http://github.com/hanklords/flickraw
9
+
10
+ = Installation
11
+ Type this in a console (you might need to be superuser)
12
+ gem install flickraw
13
+
14
+ This will recreate the documentation by fetching the methods descriptions from flickr and then virtually plugging them in standard rdoc documentation.
15
+ $ cd flickraw
16
+ $ rake rdoc
17
+
18
+ = Features
19
+
20
+ * Small single file: flickraw.rb is less than 400 lines
21
+ * Minimal dependencies
22
+ * Complete support of flickr API. This doesn't require an update of the library
23
+ * Ruby syntax similar to the flickr api
24
+ * Flickr authentication
25
+ * Photo upload
26
+ * Proxy support
27
+ * Flickr URLs helpers
28
+
29
+ = Usage
30
+
31
+ == Simple
32
+
33
+ require 'flickraw'
34
+
35
+ FlickRaw.api_key="... Your API key ..."
36
+ FlickRaw.shared_secret="... Your shared secret ..."
37
+
38
+ list = flickr.photos.getRecent
39
+
40
+ id = list[0].id
41
+ secret = list[0].secret
42
+ info = flickr.photos.getInfo :photo_id => id, :secret => secret
43
+
44
+ puts info.title # => "PICT986"
45
+ puts info.dates.taken # => "2006-07-06 15:16:18"
46
+
47
+
48
+ sizes = flickr.photos.getSizes :photo_id => id
49
+
50
+ original = sizes.find {|s| s.label == 'Original' }
51
+ puts original.width # => "800" -- may fail if they have no original marked image
52
+
53
+ == Authentication
54
+
55
+ require 'flickraw'
56
+
57
+ FlickRaw.api_key="... Your API key ..."
58
+ FlickRaw.shared_secret="... Your shared secret ..."
59
+
60
+ token = flickr.get_request_token(:perms => 'delete')
61
+ auth_url = token['oauth_authorize_url']
62
+
63
+ puts "Open this url in your process to complete the authication process : #{auth_url}"
64
+ puts "Copy here the number given when you complete the process."
65
+ verify = gets.strip
66
+
67
+ begin
68
+ flickr.get_access_token(token['oauth_token'], token['oauth_token_secret'], verify)
69
+ login = flickr.test.login
70
+ puts "You are now authenticated as #{login.username}"
71
+ rescue FlickRaw::FailedResponse => e
72
+ puts "Authentication failed : #{e.msg}"
73
+ end
74
+
75
+ If the user has already been authenticated, you can reuse the access token and access secret:
76
+
77
+ require 'flickraw'
78
+
79
+ FlickRaw.api_key="... Your API key ..."
80
+ FlickRaw.shared_secret="... Your shared secret ..."
81
+
82
+ flickr.access_token = "... Your access token ..."
83
+ flickr.access_secret = "... Your access secret ..."
84
+
85
+ # From here you are logged:
86
+ login = flickr.test.login
87
+ puts "You are now authenticated as #{login.username}"
88
+
89
+ == Upload
90
+
91
+ require 'flickraw'
92
+
93
+ FlickRaw.api_key="... Your API key ..."
94
+ FlickRaw.shared_secret="... Your shared secret ..."
95
+
96
+ PHOTO_PATH='photo.jpg'
97
+
98
+ # You need to be authentified to do that, see the previous examples.
99
+ flickr.upload_photo PHOTO_PATH, :title => "Title", :description => "This is the description"
100
+
101
+ == Proxy
102
+
103
+ require 'flickraw'
104
+ FlickRaw.proxy = "http://user:pass@proxy.example.com:3129/"
105
+
106
+ == Flickr URL Helpers
107
+
108
+ There are some helpers to build flickr urls :
109
+
110
+ === url, url_m, url_s, url_t, url_b, url_z, url_o
111
+
112
+ info = flickr.photos.getInfo(:photo_id => "3839885270")
113
+ FlickRaw.url_b(info) # => "http://farm3.static.flickr.com/2485/3839885270_6fb8b54e06_b.jpg"
114
+
115
+ === url_profile
116
+
117
+ info = flickr.photos.getInfo(:photo_id => "3839885270")
118
+ FlickRaw.url_profile(info) # => "http://www.flickr.com/people/41650587@N02/"
119
+
120
+ === url_photopage
121
+
122
+ info = flickr.photos.getInfo(:photo_id => "3839885270")
123
+ FlickRaw.url_photopage(info) # => "http://www.flickr.com/photos/41650587@N02/3839885270"
124
+
125
+ === url_photoset, url_photosets
126
+
127
+ info = flickr.photos.getInfo(:photo_id => "3839885270")
128
+ FlickRaw.url_photosets(info) # => "http://www.flickr.com/photos/41650587@N02/sets/"
129
+
130
+ === url_short, url_short_m, url_short_s, url_short_t
131
+
132
+ info = flickr.photos.getInfo(:photo_id => "3839885270")
133
+ FlickRaw.url_short(info) # => "http://flic.kr/p/6Rjq7s"
134
+
135
+ === url_photostream
136
+
137
+ info = flickr.photos.getInfo(:photo_id => "3839885270")
138
+ FlickRaw.url_photostream(info) # => "http://www.flickr.com/photos/41650587@N02/"
139
+
140
+
141
+ See the _examples_ directory to find more examples.
142
+
143
+ == Cached version
144
+
145
+ You can use
146
+
147
+ require 'flickraw-cached'
148
+
149
+ instead of
150
+
151
+ require 'flickraw'
152
+
153
+ This way it it doesn't fetch available flickr methods each time it is loaded.
154
+ The flickraw-cached gem is on rubygems.org and should be up-to-date with regard to flickr api most of the time.
155
+
156
+ = Notes
157
+
158
+ If you want to use the api authenticated with several user at the same time, you must pass the authentication token explicitely each time.
159
+ This is because it is keeping the authentication token internally.
160
+ As an alternative, you can create new Flickr objects besides Kernel.flickr which is created for you. Use Flickr.new to this effect.
161
+
@@ -0,0 +1,24 @@
1
+ require 'flickraw'
2
+
3
+ # This is how to authenticate on flickr website.
4
+ # You need an API key for that, see http://www.flickr.com/services/api/keys/
5
+ API_KEY=''
6
+ SHARED_SECRET=''
7
+
8
+ FlickRaw.api_key=API_KEY
9
+ FlickRaw.shared_secret=SHARED_SECRET
10
+
11
+ token = flickr.get_request_token(:perms => 'delete')
12
+ auth_url = token['oauth_authorize_url']
13
+
14
+ puts "Open this url in your process to complete the authication process : #{auth_url}"
15
+ puts "Copy here the number given when you complete the process."
16
+ verify = gets.strip
17
+
18
+ begin
19
+ flickr.get_access_token(token['oauth_token'], token['oauth_token_secret'], verify)
20
+ login = flickr.test.login
21
+ puts "You are now authenticated as #{login.username} with token #{flickr.access_token} and secret #{flickr.access_secret}"
22
+ rescue FlickRaw::FailedResponse => e
23
+ puts "Authentication failed : #{e.msg}"
24
+ end
@@ -0,0 +1,6 @@
1
+ require 'flickraw'
2
+
3
+ # Get the list of the 20 most recent 'interesting photos'
4
+
5
+ list = flickr.interestingness.getList :per_page => 20
6
+ list.each {|photo| puts "'#{photo.title}' id=#{photo.id} secret=#{photo.secret}" }
@@ -0,0 +1,22 @@
1
+ require 'flickraw'
2
+
3
+ # search for pictures taken within 60 miles of new brunswick, between 1890-1920
4
+
5
+ # FlickRaw.api_key="..."
6
+ # FlickRaw.shared_secret="..."
7
+
8
+ new_b = flickr.places.find :query => "new brunswick"
9
+ latitude = new_b[0]['latitude'].to_f
10
+ longitude = new_b[0]['longitude'].to_f
11
+
12
+ # within 60 miles of new brunswick, let's use a bbox
13
+ radius = 1
14
+ args = {}
15
+ args[:bbox] = "#{longitude - radius},#{latitude - radius},#{longitude + radius},#{latitude + radius}"
16
+
17
+ # requires a limiting factor, so let's give it one
18
+ args[:min_taken_date] = '1890-01-01 00:00:00'
19
+ args[:max_taken_date] = '1920-01-01 00:00:00'
20
+ args[:accuracy] = 1 # the default is street only granularity [16], which most images aren't...
21
+ discovered_pictures = flickr.photos.search args
22
+ discovered_pictures.each{|p| url = FlickRaw.url p; puts url}
@@ -0,0 +1,17 @@
1
+ require 'flickraw'
2
+
3
+ # This is how to upload photos on flickr.
4
+ # You need to be authentified to do that.
5
+
6
+ API_KEY=''
7
+ SHARED_SECRET=''
8
+ ACCESS_TOKEN=''
9
+ ACCESS _SECRET=''
10
+ PHOTO_PATH='photo.jpg'
11
+
12
+ FlickRaw.api_key = API_KEY
13
+ FlickRaw.shared_secret = SHARED_SECRET
14
+ flickr.access_token = ACCESS_TOKEN
15
+ flickr.access_secret = ACCESS_SECRET
16
+
17
+ flickr.upload_photo PHOTO_PATH, :title => 'Title', :description => 'This is the description'
@@ -0,0 +1,131 @@
1
+ require "rdoc"
2
+ require "rdoc/parser/ruby"
3
+ require "cgi"
4
+
5
+ FLICKR_API_URL='http://www.flickr.com/services/api'
6
+
7
+ FakedToken = Struct.new :text
8
+
9
+ module RDoc
10
+ class FlickrawParser < Parser::Ruby
11
+ parse_files_matching(/flickraw\.rb$/)
12
+
13
+ def scan
14
+ super
15
+
16
+ fr = @top_level.find_module_named 'FlickRaw'
17
+ k = fr.add_class NormalClass, 'Flickr', 'FlickRaw::Request'
18
+ k.record_location @top_level
19
+ @stats.add_class 'Flickr'
20
+
21
+ add_flickr_methods(FlickRaw::Flickr, k)
22
+ @top_level
23
+ end
24
+
25
+ private
26
+ def add_flickr_methods(obj, doc)
27
+ flickr # Force loading of methods if lazyloaded
28
+ obj.constants.each { |const_name|
29
+ const = obj.const_get const_name
30
+ if const.is_a?(Class) && const < FlickRaw::Request
31
+ name = const.name.sub(/.*::/, '')
32
+ k = doc.add_class NormalClass, name, 'FlickRaw::Request'
33
+ k.record_location @top_level
34
+ @stats.add_class name
35
+
36
+ m = AnyMethod.new nil, name.downcase
37
+ m.comment = "Returns a #{name} object."
38
+ m.params = ''
39
+ m.singleton = false
40
+ doc.add_method m
41
+ @stats.add_method m
42
+
43
+ add_flickr_methods(const, k)
44
+ end
45
+ }
46
+
47
+ obj.flickr_methods.each {|name|
48
+ flickr_method = obj.request_name + '.' + name
49
+ info = flickr.reflection.getMethodInfo :method_name => flickr_method
50
+
51
+ m = AnyMethod.new nil, name
52
+ m.comment = flickr_method_comment(info)
53
+ m.params = flickr_method_args(info)
54
+ m.singleton = false
55
+
56
+ m.start_collecting_tokens
57
+ m.add_token FakedToken.new( %{
58
+ # Generated automatically from flickr api
59
+ def #{name}(*args)
60
+ @flickr.call '#{flickr_method}', *args
61
+ end
62
+ } )
63
+ doc.add_method m
64
+ @stats.add_method m
65
+ }
66
+ end
67
+
68
+ def flickr_method_comment(info)
69
+ description = CGI.unescapeHTML(info.method.description.to_s)
70
+ # description.gsub!( /<\/?(\w+)>/ ) {|b|
71
+ # return b if ['em', 'b', 'tt'].include? $1
72
+ # return ''
73
+ # }
74
+
75
+ if info.respond_to? :arguments
76
+ args = info.arguments.select { |arg| arg.name != 'api_key' }
77
+
78
+ arguments = "<b>Arguments</b>\n"
79
+ if args.size > 0
80
+ args.each {|arg|
81
+ arguments << "[#{arg.name} "
82
+ arguments << "<em>(required)</em> " if arg.optional == '0'
83
+ arguments << "] "
84
+ arguments << "#{CGI.unescapeHTML(arg.to_s)}\n"
85
+ }
86
+ end
87
+ end
88
+
89
+ if info.respond_to? :errors
90
+ errors = "<b>Error codes</b>\n"
91
+ info.errors.each {|e|
92
+ errors << "* #{e.code}: <em>#{e.message}</em>\n\n"
93
+ errors << " #{CGI.unescapeHTML e.to_s}\n"
94
+ }
95
+ end
96
+
97
+ if info.method.respond_to? :response
98
+ response = "<b>Returns</b>\n"
99
+ raw = CGI.unescapeHTML(info.method.response.to_s)
100
+ response << raw.lines.collect { |line| line.insert(0, ' ') }.join
101
+ else
102
+ response = ''
103
+ end
104
+
105
+ str = "{#{info.method.name}}[#{FLICKR_API_URL}/#{info.method.name}.html] request.\n\n"
106
+ str << description << "\n\n"
107
+ str << arguments << "\n\n"
108
+ str << errors << "\n\n"
109
+ str << response << "\n\n"
110
+ end
111
+
112
+ def flickr_method_args(info)
113
+ str = ''
114
+ if info.respond_to? :arguments
115
+ args = info.arguments.select { |arg| arg.name != 'api_key' }
116
+
117
+ if args.size > 0
118
+ str << '('
119
+ args.each {|arg|
120
+ str << ":#{arg.name} => '#{arg.name}'"
121
+ str << ','
122
+ }
123
+ str.chomp! ','
124
+ str << ')'
125
+ end
126
+ end
127
+ str
128
+ end
129
+
130
+ end
131
+ end
@@ -0,0 +1,413 @@
1
+ # encoding: ascii-8bit
2
+ # Copyright (c) 2006 Mael Clerambault <maelclerambault@yahoo.fr>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require 'net/http'
24
+ require 'json'
25
+
26
+ module FlickRaw
27
+ VERSION='0.8.4.1'
28
+ USER_AGENT = "Flickraw/#{VERSION}"
29
+
30
+ FLICKR_OAUTH_REQUEST_TOKEN='http://www.flickr.com/services/oauth/request_token'.freeze
31
+ FLICKR_OAUTH_AUTHORIZE='http://www.flickr.com/services/oauth/authorize'.freeze
32
+ FLICKR_OAUTH_ACCESS_TOKEN='http://www.flickr.com/services/oauth/access_token'.freeze
33
+
34
+ REST_PATH='http://api.flickr.com/services/rest/'.freeze
35
+ UPLOAD_PATH='http://api.flickr.com/services/upload/'.freeze
36
+ REPLACE_PATH='http://api.flickr.com/services/replace/'.freeze
37
+
38
+ PHOTO_SOURCE_URL='http://farm%s.static.flickr.com/%s/%s_%s%s.%s'.freeze
39
+ URL_PROFILE='http://www.flickr.com/people/'.freeze
40
+ URL_PHOTOSTREAM='http://www.flickr.com/photos/'.freeze
41
+ URL_SHORT='http://flic.kr/p/'.freeze
42
+
43
+ class OAuth
44
+ class FailedResponse < StandardError
45
+ def initialize(str)
46
+ @response = OAuth.parse_response(str)
47
+ super(@response['oauth_problem'])
48
+ end
49
+ end
50
+
51
+ class << self
52
+ def escape(v); URI.escape(v.to_s, /[^a-zA-Z0-9\-\.\_\~]/) end
53
+ def parse_response(text); Hash[text.split("&").map {|s| s.split("=")}] end
54
+ end
55
+
56
+ attr_accessor :user_agent
57
+ attr_reader :proxy
58
+ def proxy=(url)
59
+ return if url.nil?
60
+ @proxy = URI.parse(url)
61
+ @proxy_host, @proxy_port, @proxy_user, @proxy_password = @proxy.host, @proxy.port, @proxy.user, @proxy.password
62
+ @proxy
63
+ end
64
+
65
+ def initialize(consumer_key, consumer_secret); @consumer_key, @consumer_secret = consumer_key, consumer_secret end
66
+
67
+ def sign(method, url, params, token_secret = nil, consumer_secret = @consumer_secret)
68
+ params_norm = params.map {|k,v| OAuth.escape(k) + "=" + OAuth.escape(v) }.sort.join("&")
69
+ text = method.to_s.upcase + "&" + OAuth.escape(url) + "&" + OAuth.escape(params_norm)
70
+ key = consumer_secret.to_s + "&" + token_secret.to_s
71
+ digest = OpenSSL::Digest::Digest.new("sha1")
72
+ [OpenSSL::HMAC.digest(digest, key, text)].pack('m0').gsub(/\n$/,'')
73
+ end
74
+
75
+ def authorization_header(url, params)
76
+ params_norm = params.map {|k,v| OAuth.escape(k) + "=\"" + OAuth.escape(v) + "\""}.sort.join(", ")
77
+ "OAuth realm=\"" + url.to_s + "\", " + params_norm
78
+ end
79
+
80
+ def gen_timestamp; Time.now.to_i end
81
+ def gen_nonce; [OpenSSL::Random.random_bytes(32)].pack('m0').gsub(/\n$/,'') end
82
+ def gen_default_params
83
+ { :oauth_version => "1.0", :oauth_signature_method => "HMAC-SHA1",
84
+ :oauth_consumer_key => @consumer_key, :oauth_nonce => gen_nonce,
85
+ :oauth_timestamp => gen_timestamp }
86
+ end
87
+
88
+ def request_token(url, oauth_params = {})
89
+ r = post_form(url, nil, {:oauth_callback => "oob"}.merge(oauth_params))
90
+ OAuth.parse_response(r.body)
91
+ end
92
+
93
+ def authorize_url(url, oauth_params = {})
94
+ params_norm = oauth_params.map {|k,v| OAuth.escape(k) + "=" + OAuth.escape(v)}.sort.join("&")
95
+ url = URI.parse(url)
96
+ url.query = url.query ? url.query + "&" + params_norm : params_norm
97
+ url
98
+ end
99
+
100
+ def access_token(url, token_secret, oauth_params = {})
101
+ r = post_form(url, token_secret, oauth_params)
102
+ OAuth.parse_response(r.body)
103
+ end
104
+
105
+ def post_form(url, token_secret, oauth_params = {}, params = {})
106
+ oauth_params = gen_default_params.merge(oauth_params)
107
+ oauth_params[:oauth_signature] = sign(:post, url, params.merge(oauth_params), token_secret)
108
+ url = URI.parse(url)
109
+ r = Net::HTTP.start(url.host, url.port, @proxy_host, @proxy_port, @proxy_user, @proxy_password) { |http|
110
+ request = Net::HTTP::Post.new(url.path)
111
+ request['User-Agent'] = @user_agent if @user_agent
112
+ request['Authorization'] = authorization_header(url, oauth_params)
113
+ request.form_data = params
114
+ http.request(request)
115
+ }
116
+
117
+ raise FailedResponse.new(r.body) if r.is_a? Net::HTTPClientError
118
+ r
119
+ end
120
+
121
+ def post_multipart(url, token_secret, oauth_params = {}, params = {})
122
+ oauth_params = gen_default_params.merge(oauth_params)
123
+ params_signed = params.reject {|k,v| v.is_a? File}.merge(oauth_params)
124
+ oauth_params[:oauth_signature] = sign(:post, url, params_signed, token_secret)
125
+ url = URI.parse(url)
126
+ r = Net::HTTP.start(url.host, url.port, @proxy_host, @proxy_port, @proxy_user, @proxy_password) { |http|
127
+ boundary = "FlickRaw#{gen_nonce}"
128
+ request = Net::HTTP::Post.new(url.path)
129
+ request['User-Agent'] = @user_agent if @user_agent
130
+ request['Content-type'] = "multipart/form-data, boundary=#{boundary}"
131
+ request['Authorization'] = authorization_header(url, oauth_params)
132
+
133
+ request.body = ''
134
+ params.each { |k, v|
135
+ if v.is_a? File
136
+ basename = File.basename(v.path).to_s
137
+ basename = basename.encode("utf-8").force_encoding("ascii-8bit") if RUBY_VERSION >= "1.9"
138
+ filename = basename
139
+ request.body << "--#{boundary}\r\n" <<
140
+ "Content-Disposition: form-data; name=\"#{k}\"; filename=\"#{filename}\"\r\n" <<
141
+ "Content-Transfer-Encoding: binary\r\n" <<
142
+ "Content-Type: image/jpeg\r\n\r\n" <<
143
+ v.read << "\r\n"
144
+ else
145
+ request.body << "--#{boundary}\r\n" <<
146
+ "Content-Disposition: form-data; name=\"#{k}\"\r\n\r\n" <<
147
+ "#{v}\r\n"
148
+ end
149
+ }
150
+
151
+ request.body << "--#{boundary}--"
152
+ http.request(request)
153
+ }
154
+
155
+ raise FailedResponse.new(r.body) if r.is_a? Net::HTTPClientError
156
+ r
157
+ end
158
+ end
159
+
160
+ class Response
161
+ def self.build(h, type) # :nodoc:
162
+ if h.is_a? Response
163
+ h
164
+ elsif type =~ /s$/ and (a = h[$`]).is_a? Array
165
+ ResponseList.new(h, type, a.collect {|e| Response.build(e, $`)})
166
+ elsif h.keys == ["_content"]
167
+ h["_content"]
168
+ else
169
+ Response.new(h, type)
170
+ end
171
+ end
172
+
173
+ attr_reader :flickr_type
174
+ def initialize(h, type) # :nodoc:
175
+ @flickr_type, @h = type, {}
176
+ methods = "class << self;"
177
+ h.each {|k,v|
178
+ @h[k] = case v
179
+ when Hash then Response.build(v, k)
180
+ when Array then v.collect {|e| Response.build(e, k)}
181
+ else v
182
+ end
183
+ methods << "def #{k}; @h['#{k}'] end;"
184
+ }
185
+ eval methods << "end"
186
+ end
187
+ def [](k); @h[k] end
188
+ def to_s; @h["_content"] || super end
189
+ def inspect; @h.inspect end
190
+ def to_hash; @h end
191
+ def marshal_dump; [@h, @flickr_type] end
192
+ def marshal_load(data); initialize(*data) end
193
+ end
194
+
195
+ class ResponseList < Response
196
+ include Enumerable
197
+ def initialize(h, t, a); super(h, t); @a = a end
198
+ def [](k); k.is_a?(Fixnum) ? @a[k] : super(k) end
199
+ def each; @a.each{|e| yield e} end
200
+ def to_a; @a end
201
+ def inspect; @a.inspect end
202
+ def size; @a.size end
203
+ def marshal_dump; [@h, @flickr_type, @a] end
204
+ alias length size
205
+ end
206
+
207
+ class FailedResponse < StandardError
208
+ attr_reader :code
209
+ alias :msg :message
210
+ def initialize(msg, code, req)
211
+ @code = code
212
+ super("'#{req}' - #{msg}")
213
+ end
214
+ end
215
+
216
+ class Request
217
+ def initialize(flickr = nil) # :nodoc:
218
+ @flickr = flickr
219
+
220
+ self.class.flickr_objects.each {|name|
221
+ klass = self.class.const_get name.capitalize
222
+ instance_variable_set "@#{name}", klass.new(@flickr)
223
+ }
224
+ end
225
+
226
+ def self.build_request(req) # :nodoc:
227
+ method_nesting = req.split '.'
228
+ raise "'#{@name}' : Method name mismatch" if method_nesting.shift != request_name.split('.').last
229
+
230
+ if method_nesting.size > 1
231
+ name = method_nesting.first
232
+ class_name = name.capitalize
233
+ if flickr_objects.include? name
234
+ klass = const_get(class_name)
235
+ else
236
+ klass = Class.new Request
237
+ const_set(class_name, klass)
238
+ attr_reader name
239
+ flickr_objects << name
240
+ end
241
+
242
+ klass.build_request method_nesting.join('.')
243
+ else
244
+ req = method_nesting.first
245
+ module_eval %{
246
+ def #{req}(*args, &block)
247
+ @flickr.call("#{request_name}.#{req}", *args, &block)
248
+ end
249
+ }
250
+ flickr_methods << req
251
+ end
252
+ end
253
+
254
+ # List of the flickr subobjects of this object
255
+ def self.flickr_objects; @flickr_objects ||= [] end
256
+
257
+ # List of the flickr methods of this object
258
+ def self.flickr_methods; @flickr_methods ||= [] end
259
+
260
+ # Returns the prefix of the request corresponding to this class.
261
+ def self.request_name; name.downcase.gsub(/::/, '.').sub(/[^\.]+\./, '') end
262
+ end
263
+
264
+ # Root class of the flickr api hierarchy.
265
+ class Flickr < Request
266
+ attr_accessor :access_token, :access_secret
267
+
268
+ def self.build(methods); methods.each { |m| build_request m } end
269
+
270
+ def initialize # :nodoc:
271
+ raise "No API key or secret defined !" if FlickRaw.api_key.nil? or FlickRaw.shared_secret.nil?
272
+ @oauth_consumer = OAuth.new(FlickRaw.api_key, FlickRaw.shared_secret)
273
+ @oauth_consumer.proxy = FlickRaw.proxy
274
+ @oauth_consumer.user_agent = USER_AGENT
275
+
276
+ Flickr.build(call('flickr.reflection.getMethods')) if Flickr.flickr_objects.empty?
277
+ super self
278
+ end
279
+
280
+ # This is the central method. It does the actual request to the flickr server.
281
+ #
282
+ # Raises FailedResponse if the response status is _failed_.
283
+ def call(req, args={}, &block)
284
+ http_response = @oauth_consumer.post_form(REST_PATH, @access_secret, {:oauth_token => @access_token}, build_args(args, req))
285
+ process_response(req, http_response.body)
286
+ end
287
+
288
+ def get_request_token(args = {})
289
+ request_token = @oauth_consumer.request_token(FLICKR_OAUTH_REQUEST_TOKEN)
290
+ authorize_url = @oauth_consumer.authorize_url(FLICKR_OAUTH_AUTHORIZE, args.merge(:oauth_token => request_token['oauth_token']))
291
+ request_token.merge('oauth_authorize_url' => authorize_url)
292
+ end
293
+
294
+ def get_access_token(token, secret, verify)
295
+ access_token = @oauth_consumer.access_token(FLICKR_OAUTH_ACCESS_TOKEN, secret, :oauth_token => token, :oauth_verifier => verify)
296
+ @access_token, @access_secret = access_token['oauth_token'], access_token['oauth_token_secret']
297
+ access_token
298
+ end
299
+
300
+ # Use this to upload the photo in _file_.
301
+ #
302
+ # flickr.upload_photo '/path/to/the/photo', :title => 'Title', :description => 'This is the description'
303
+ #
304
+ # See http://www.flickr.com/services/api/upload.api.html for more information on the arguments.
305
+ def upload_photo(file, args={}); upload_flickr(UPLOAD_PATH, file, args) end
306
+
307
+ # Use this to replace the photo with :photo_id with the photo in _file_.
308
+ #
309
+ # flickr.replace_photo '/path/to/the/photo', :photo_id => id
310
+ #
311
+ # See http://www.flickr.com/services/api/replace.api.html for more information on the arguments.
312
+ def replace_photo(file, args={}); upload_flickr(REPLACE_PATH, file, args) end
313
+
314
+ private
315
+ def build_args(args={}, method = nil)
316
+ full_args = {'format' => 'json', :nojsoncallback => "1"}
317
+ full_args['method'] = method if method
318
+ args.each {|k, v|
319
+ v = v.to_s.encode("utf-8").force_encoding("ascii-8bit") if RUBY_VERSION >= "1.9"
320
+ full_args[k.to_s] = v
321
+ }
322
+ full_args
323
+ end
324
+
325
+ def process_response(req, response)
326
+ if response =~ /^<\?xml / # upload_photo returns xml data whatever we ask
327
+ if response[/stat="(\w+)"/, 1] == 'fail'
328
+ msg = response[/msg="([^"]+)"/, 1]
329
+ code = response[/code="([^"]+)"/, 1]
330
+ raise FailedResponse.new(msg, code, req)
331
+ end
332
+
333
+ type = response[/<(\w+)/, 1]
334
+ h = {
335
+ "secret" => response[/secret="([^"]+)"/, 1],
336
+ "originalsecret" => response[/originalsecret="([^"]+)"/, 1],
337
+ "_content" => response[/>([^<]+)<\//, 1]
338
+ }.delete_if {|k,v| v.nil? }
339
+
340
+ Response.build h, type
341
+ else
342
+ json = JSON.load(response.empty? ? "{}" : response)
343
+ raise FailedResponse.new(json['message'], json['code'], req) if json.delete('stat') == 'fail'
344
+ type, json = json.to_a.first if json.size == 1 and json.all? {|k,v| v.is_a? Hash}
345
+
346
+ Response.build json, type
347
+ end
348
+ end
349
+
350
+ def upload_flickr(method, file, args={})
351
+ args = build_args(args)
352
+ args['photo'] = open(file, 'rb')
353
+ http_response = @oauth_consumer.post_multipart(method, @access_secret, {:oauth_token => @access_token}, args)
354
+ process_response(method, http_response.body)
355
+ end
356
+ end
357
+
358
+ class << self
359
+ # Your flickr API key, see http://www.flickr.com/services/api/keys for more information
360
+ attr_accessor :api_key
361
+
362
+ # The shared secret of _api_key_, see http://www.flickr.com/services/api/keys for more information
363
+ attr_accessor :shared_secret
364
+
365
+ # Use a proxy
366
+ attr_accessor :proxy
367
+
368
+ BASE58_ALPHABET="123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ".freeze
369
+ def base58(id)
370
+ id = id.to_i
371
+ alphabet = BASE58_ALPHABET.split(//)
372
+ base = alphabet.length
373
+ begin
374
+ id, m = id.divmod(base)
375
+ r = alphabet[m] + (r || '')
376
+ end while id > 0
377
+ r
378
+ end
379
+
380
+ def url(r); PHOTO_SOURCE_URL % [r.farm, r.server, r.id, r.secret, "", "jpg"] end
381
+ def url_m(r); PHOTO_SOURCE_URL % [r.farm, r.server, r.id, r.secret, "_m", "jpg"] end
382
+ def url_s(r); PHOTO_SOURCE_URL % [r.farm, r.server, r.id, r.secret, "_s", "jpg"] end
383
+ def url_t(r); PHOTO_SOURCE_URL % [r.farm, r.server, r.id, r.secret, "_t", "jpg"] end
384
+ def url_b(r); PHOTO_SOURCE_URL % [r.farm, r.server, r.id, r.secret, "_b", "jpg"] end
385
+ def url_z(r); PHOTO_SOURCE_URL % [r.farm, r.server, r.id, r.secret, "_z", "jpg"] end
386
+ def url_o(r); PHOTO_SOURCE_URL % [r.farm, r.server, r.id, r.originalsecret, "_o", r.originalformat] end
387
+ def url_profile(r); URL_PROFILE + (r.owner.respond_to?(:nsid) ? r.owner.nsid : r.owner) + "/" end
388
+ def url_photopage(r); url_photostream(r) + r.id end
389
+ def url_photosets(r); url_photostream(r) + "sets/" end
390
+ def url_photoset(r); url_photosets(r) + r.id end
391
+ def url_short(r); URL_SHORT + base58(r.id) end
392
+ def url_short_m(r); URL_SHORT + "img/" + base58(r.id) + "_m.jpg" end
393
+ def url_short_s(r); URL_SHORT + "img/" + base58(r.id) + ".jpg" end
394
+ def url_short_t(r); URL_SHORT + "img/" + base58(r.id) + "_t.jpg" end
395
+ def url_photostream(r)
396
+ URL_PHOTOSTREAM +
397
+ if r.respond_to?(:pathalias) and r.pathalias
398
+ r.pathalias
399
+ elsif r.owner.respond_to?(:nsid)
400
+ r.owner.nsid
401
+ else
402
+ r.owner
403
+ end + "/"
404
+ end
405
+ end
406
+ end
407
+
408
+ # Use this to access the flickr API easily. You can type directly the flickr requests as they are described on the flickr website.
409
+ # require 'flickraw'
410
+ #
411
+ # recent_photos = flickr.photos.getRecent
412
+ # puts recent_photos[0].title
413
+ #def flickr; $flickraw ||= FlickRaw::Flickr.new end