genki-flickraw 0.8.4.1

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/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