reagent-fleakr 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +115 -20
- data/Rakefile +1 -1
- data/lib/fleakr.rb +66 -7
- data/lib/fleakr/api.rb +7 -0
- data/lib/fleakr/api/file_parameter.rb +47 -0
- data/lib/fleakr/api/method_request.rb +57 -0
- data/lib/fleakr/api/parameter.rb +35 -0
- data/lib/fleakr/api/parameter_list.rb +96 -0
- data/lib/fleakr/api/response.rb +2 -2
- data/lib/fleakr/api/upload_request.rb +64 -0
- data/lib/fleakr/api/value_parameter.rb +36 -0
- data/lib/fleakr/core_ext.rb +1 -0
- data/lib/fleakr/core_ext/hash.rb +22 -0
- data/lib/fleakr/objects.rb +9 -0
- data/lib/fleakr/objects/authentication_token.rb +43 -0
- data/lib/fleakr/objects/contact.rb +5 -5
- data/lib/fleakr/objects/error.rb +2 -2
- data/lib/fleakr/objects/group.rb +2 -2
- data/lib/fleakr/objects/image.rb +7 -7
- data/lib/fleakr/objects/photo.rb +69 -5
- data/lib/fleakr/objects/search.rb +3 -6
- data/lib/fleakr/objects/set.rb +11 -5
- data/lib/fleakr/objects/user.rb +14 -26
- data/lib/fleakr/support.rb +2 -0
- data/lib/fleakr/support/attribute.rb +30 -12
- data/lib/fleakr/support/object.rb +20 -4
- data/lib/fleakr/version.rb +1 -1
- data/test/fixtures/auth.checkToken.xml +8 -0
- data/test/fixtures/auth.getFullToken.xml +8 -0
- data/test/fixtures/people.getInfo.xml +1 -1
- data/test/fixtures/photos.getInfo.xml +20 -0
- data/test/test_helper.rb +18 -3
- data/test/unit/fleakr/api/file_parameter_test.rb +63 -0
- data/test/unit/fleakr/api/method_request_test.rb +103 -0
- data/test/unit/fleakr/api/parameter_list_test.rb +161 -0
- data/test/unit/fleakr/api/parameter_test.rb +34 -0
- data/test/unit/fleakr/api/upload_request_test.rb +133 -0
- data/test/unit/fleakr/api/value_parameter_test.rb +41 -0
- data/test/unit/fleakr/core_ext/hash_test.rb +32 -0
- data/test/unit/fleakr/objects/authentication_token_test.rb +47 -0
- data/test/unit/fleakr/objects/image_test.rb +10 -5
- data/test/unit/fleakr/objects/photo_test.rb +96 -36
- data/test/unit/fleakr/objects/search_test.rb +1 -1
- data/test/unit/fleakr/objects/set_test.rb +12 -1
- data/test/unit/fleakr/objects/user_test.rb +2 -16
- data/test/unit/fleakr/support/attribute_test.rb +82 -24
- data/test/unit/fleakr/support/object_test.rb +26 -3
- data/test/unit/fleakr_test.rb +65 -6
- metadata +27 -4
- data/lib/fleakr/api/request.rb +0 -58
- data/test/unit/fleakr/api/request_test.rb +0 -93
@@ -0,0 +1,96 @@
|
|
1
|
+
module Fleakr
|
2
|
+
module Api # :nodoc:
|
3
|
+
|
4
|
+
# = ParameterList
|
5
|
+
#
|
6
|
+
# Represents a list of parameters that get passed as part of a
|
7
|
+
# MethodRequest or UploadRequest. These can be transformed as necessary
|
8
|
+
# into query strings (using #to_query) or form data (using #to_form)
|
9
|
+
#
|
10
|
+
class ParameterList
|
11
|
+
|
12
|
+
# Create a new parameter list with optional parameters:
|
13
|
+
# [:sign?] Will these parameters be used to sign the request?
|
14
|
+
# [:authenticate?] Will the request need to be authenticated?
|
15
|
+
#
|
16
|
+
# Any additional name / value pairs will be created as individual
|
17
|
+
# ValueParameters as part of the list. Example:
|
18
|
+
#
|
19
|
+
# >> list = Fleakr::Api::ParameterList.new(:foo => 'bar')
|
20
|
+
# => #<Fleakr::Api::ParameterList:0x1656e6c @list=... >
|
21
|
+
# >> list[:foo]
|
22
|
+
# => #<Fleakr::Api::ValueParameter:0x1656da4 @include_in_signature=true, @name="foo", @value="bar">
|
23
|
+
#
|
24
|
+
def initialize(options = {})
|
25
|
+
@api_options = options.extract!(:sign?, :authenticate?)
|
26
|
+
|
27
|
+
@list = Hash.new
|
28
|
+
|
29
|
+
options.each {|k,v| self << ValueParameter.new(k.to_s, v) }
|
30
|
+
|
31
|
+
self << ValueParameter.new('api_key', Fleakr.api_key)
|
32
|
+
self << ValueParameter.new('auth_token', Fleakr.token.value) if authenticate?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add a new parameter (ValueParameter / FileParameter) to the list
|
36
|
+
#
|
37
|
+
def <<(parameter)
|
38
|
+
@list.merge!(parameter.name => parameter)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Should this parameter list be signed?
|
42
|
+
#
|
43
|
+
def sign?
|
44
|
+
(@api_options[:sign?] == true || authenticate?) ? true : false
|
45
|
+
end
|
46
|
+
|
47
|
+
# Should we send the auth_token with the request?
|
48
|
+
#
|
49
|
+
def authenticate?
|
50
|
+
(@api_options[:authenticate?] == true) ? true : false
|
51
|
+
end
|
52
|
+
|
53
|
+
# Access an individual parameter by key (symbol or string)
|
54
|
+
#
|
55
|
+
def [](key)
|
56
|
+
list[key.to_s]
|
57
|
+
end
|
58
|
+
|
59
|
+
def boundary # :nodoc:
|
60
|
+
@boundary ||= Digest::MD5.hexdigest(rand.to_s)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Generate the query string representation of this parameter
|
64
|
+
# list - e.g. <tt>foo=bar&blee=baz</tt>
|
65
|
+
#
|
66
|
+
def to_query
|
67
|
+
list.values.map(&:to_query).join('&')
|
68
|
+
end
|
69
|
+
|
70
|
+
# Generate the form representation of this parameter list including the
|
71
|
+
# boundary
|
72
|
+
#
|
73
|
+
def to_form
|
74
|
+
form = list.values.map {|p| "--#{self.boundary}\r\n#{p.to_form}" }.join
|
75
|
+
form << "--#{self.boundary}--"
|
76
|
+
|
77
|
+
form
|
78
|
+
end
|
79
|
+
|
80
|
+
def signature # :nodoc:
|
81
|
+
parameters_to_sign = @list.values.reject {|p| !p.include_in_signature? }
|
82
|
+
signature_text = parameters_to_sign.sort.map {|p| "#{p.name}#{p.value}" }.join
|
83
|
+
|
84
|
+
Digest::MD5.hexdigest("#{Fleakr.shared_secret}#{signature_text}")
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
def list
|
89
|
+
list = @list
|
90
|
+
list.merge!('api_sig' => ValueParameter.new('api_sig', signature, false)) if self.sign?
|
91
|
+
|
92
|
+
list
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/fleakr/api/response.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module Fleakr
|
2
|
-
module Api
|
2
|
+
module Api # :nodoc:
|
3
3
|
|
4
4
|
# = Response
|
5
5
|
#
|
6
6
|
# Response objects contain Hpricot documents that are traversed and parsed by
|
7
7
|
# the model objects. This class is never called directly but is instantiated
|
8
|
-
# during the request cycle (see: Fleakr::Api::
|
8
|
+
# during the request cycle (see: Fleakr::Api::MethodRequest.with_response!)
|
9
9
|
#
|
10
10
|
class Response
|
11
11
|
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Fleakr
|
2
|
+
module Api # :nodoc:
|
3
|
+
|
4
|
+
# = UploadRequest
|
5
|
+
#
|
6
|
+
# This implements the upload functionality of the Flickr API which is needed
|
7
|
+
# to create new photos and replace the photo content of existing photos
|
8
|
+
#
|
9
|
+
class UploadRequest
|
10
|
+
|
11
|
+
ENDPOINT_URIS = {
|
12
|
+
:create => 'http://api.flickr.com/services/upload/',
|
13
|
+
:update => 'http://api.flickr.com/services/replace/'
|
14
|
+
}
|
15
|
+
|
16
|
+
attr_reader :parameters, :type
|
17
|
+
|
18
|
+
# Send a request and return a Response object. If an API error occurs, this raises
|
19
|
+
# a Fleakr::ApiError with the reason for the error. See UploadRequest#new for more
|
20
|
+
# details.
|
21
|
+
#
|
22
|
+
def self.with_response!(filename, options = {})
|
23
|
+
request = self.new(filename, options)
|
24
|
+
response = request.send
|
25
|
+
|
26
|
+
raise(Fleakr::ApiError, "Code: #{response.error.code} - #{response.error.message}") if response.error?
|
27
|
+
|
28
|
+
response
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create a new UploadRequest with the specified filename and options:
|
32
|
+
#
|
33
|
+
# [:type] Valid values are :create and :update and are used when uploading new
|
34
|
+
# photos or replacing existing ones
|
35
|
+
#
|
36
|
+
def initialize(filename, options = {})
|
37
|
+
type_options = options.extract!(:type)
|
38
|
+
options.merge!(:authenticate? => true)
|
39
|
+
|
40
|
+
@type = type_options[:type] || :create
|
41
|
+
|
42
|
+
@parameters = ParameterList.new(options)
|
43
|
+
@parameters << FileParameter.new('photo', filename)
|
44
|
+
end
|
45
|
+
|
46
|
+
def headers # :nodoc:
|
47
|
+
{'Content-Type' => "multipart/form-data; boundary=#{self.parameters.boundary}"}
|
48
|
+
end
|
49
|
+
|
50
|
+
def send # :nodoc:
|
51
|
+
response = Net::HTTP.start(endpoint_uri.host, endpoint_uri.port) do |http|
|
52
|
+
http.post(endpoint_uri.path, self.parameters.to_form, self.headers)
|
53
|
+
end
|
54
|
+
Response.new(response.body)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def endpoint_uri
|
59
|
+
@endpoint_uri ||= URI.parse(ENDPOINT_URIS[self.type])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Fleakr
|
2
|
+
module Api # :nodoc:
|
3
|
+
|
4
|
+
# = ValueParameter
|
5
|
+
#
|
6
|
+
# A simple name / value parameter for use in API calls
|
7
|
+
#
|
8
|
+
class ValueParameter < Parameter
|
9
|
+
|
10
|
+
attr_reader :value
|
11
|
+
|
12
|
+
# Create a new parameter with the specified name / value pair.
|
13
|
+
#
|
14
|
+
def initialize(name, value, include_in_signature = true)
|
15
|
+
@value = value
|
16
|
+
super(name, include_in_signature)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Generate the query string representation of this parameter.
|
20
|
+
#
|
21
|
+
def to_query
|
22
|
+
"#{self.name}=#{CGI.escape(self.value.to_s)}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Generate the form representation of this parameter.
|
26
|
+
#
|
27
|
+
def to_form
|
28
|
+
"Content-Disposition: form-data; name=\"#{self.name}\"\r\n" +
|
29
|
+
"\r\n" +
|
30
|
+
"#{self.value}\r\n"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'fleakr/core_ext/hash'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
# Extract the matching keys from the source hash and return
|
4
|
+
# a new hash with those keys:
|
5
|
+
#
|
6
|
+
# >> h = {:a => 'b', :c => 'd'}
|
7
|
+
# => {:a=>"b", :c=>"d"}
|
8
|
+
# >> h.extract!(:a)
|
9
|
+
# => {:a=>"b"}
|
10
|
+
# >> h
|
11
|
+
# => {:c=>"d"}
|
12
|
+
#
|
13
|
+
def extract!(*keys)
|
14
|
+
value = {}
|
15
|
+
|
16
|
+
keys.each {|k| value.merge!({k => self[k]}) if self.has_key?(k) }
|
17
|
+
keys.each {|k| delete(k) }
|
18
|
+
|
19
|
+
value
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'fleakr/objects/authentication_token'
|
2
|
+
require 'fleakr/objects/contact'
|
3
|
+
require 'fleakr/objects/error'
|
4
|
+
require 'fleakr/objects/group'
|
5
|
+
require 'fleakr/objects/image'
|
6
|
+
require 'fleakr/objects/photo'
|
7
|
+
require 'fleakr/objects/search'
|
8
|
+
require 'fleakr/objects/set'
|
9
|
+
require 'fleakr/objects/user'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Fleakr
|
2
|
+
module Objects # :nodoc:
|
3
|
+
|
4
|
+
# = AuthenticationToken
|
5
|
+
#
|
6
|
+
# This class represents an authentication token used for API calls that
|
7
|
+
# require authentication before they can be used
|
8
|
+
#
|
9
|
+
# == Attributes
|
10
|
+
#
|
11
|
+
# [value] The token value that is used in subsequent API calls
|
12
|
+
# [permissions] The permissions granted to this application (read / write / delete)
|
13
|
+
#
|
14
|
+
class AuthenticationToken
|
15
|
+
|
16
|
+
include Fleakr::Support::Object
|
17
|
+
|
18
|
+
flickr_attribute :value, :from => 'auth/token'
|
19
|
+
flickr_attribute :permissions, :from => 'auth/perms'
|
20
|
+
|
21
|
+
# Retrieve a full authentication token from the supplied mini-token (e.g. 123-456-789)
|
22
|
+
#
|
23
|
+
def self.from_mini_token(token)
|
24
|
+
parameters = {:mini_token => token, :sign? => true}
|
25
|
+
response = Fleakr::Api::MethodRequest.with_response!('auth.getFullToken', parameters)
|
26
|
+
|
27
|
+
self.new(response.body)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Retrieve a full authentication token from the supplied auth_token string
|
31
|
+
# (e.g. 45-76598454353455)
|
32
|
+
#
|
33
|
+
def self.from_auth_token(token)
|
34
|
+
parameters = {:auth_token => token, :sign? => true}
|
35
|
+
response = Fleakr::Api::MethodRequest.with_response!('auth.checkToken', parameters)
|
36
|
+
|
37
|
+
self.new(response.body)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -4,15 +4,15 @@ module Fleakr
|
|
4
4
|
|
5
5
|
include Fleakr::Support::Object
|
6
6
|
|
7
|
-
flickr_attribute :id, :
|
8
|
-
flickr_attribute :username
|
9
|
-
flickr_attribute :icon_server, :
|
10
|
-
flickr_attribute :icon_farm,
|
7
|
+
flickr_attribute :id, :from => '@nsid'
|
8
|
+
flickr_attribute :username
|
9
|
+
flickr_attribute :icon_server, :from => '@iconserver'
|
10
|
+
flickr_attribute :icon_farm, :from => '@iconfarm'
|
11
11
|
|
12
12
|
# Retrieve a list of contacts for the specified user ID and return an initialized
|
13
13
|
# collection of #User objects
|
14
14
|
def self.find_all_by_user_id(user_id)
|
15
|
-
response = Fleakr::Api::
|
15
|
+
response = Fleakr::Api::MethodRequest.with_response!('contacts.getPublicList', :user_id => user_id)
|
16
16
|
(response.body/'contacts/contact').map {|c| Contact.new(c).to_user }
|
17
17
|
end
|
18
18
|
|
data/lib/fleakr/objects/error.rb
CHANGED
@@ -14,8 +14,8 @@ module Fleakr
|
|
14
14
|
|
15
15
|
include Fleakr::Support::Object
|
16
16
|
|
17
|
-
flickr_attribute :code, :
|
18
|
-
flickr_attribute :message, :
|
17
|
+
flickr_attribute :code, :from => 'err@code'
|
18
|
+
flickr_attribute :message, :from => 'err@msg'
|
19
19
|
|
20
20
|
end
|
21
21
|
end
|
data/lib/fleakr/objects/group.rb
CHANGED
@@ -12,8 +12,8 @@ module Fleakr
|
|
12
12
|
|
13
13
|
include Fleakr::Support::Object
|
14
14
|
|
15
|
-
flickr_attribute :id, :
|
16
|
-
flickr_attribute :name
|
15
|
+
flickr_attribute :id, :from => '@nsid'
|
16
|
+
flickr_attribute :name
|
17
17
|
|
18
18
|
find_all :by_user_id, :call => 'people.getPublicGroups', :path => 'groups/group'
|
19
19
|
|
data/lib/fleakr/objects/image.rb
CHANGED
@@ -24,11 +24,11 @@ module Fleakr
|
|
24
24
|
|
25
25
|
include Fleakr::Support::Object
|
26
26
|
|
27
|
-
flickr_attribute :size, :
|
28
|
-
flickr_attribute :width
|
29
|
-
flickr_attribute :height
|
30
|
-
flickr_attribute :url, :
|
31
|
-
flickr_attribute :page, :
|
27
|
+
flickr_attribute :size, :from => '@label'
|
28
|
+
flickr_attribute :width
|
29
|
+
flickr_attribute :height
|
30
|
+
flickr_attribute :url, :from => '@source'
|
31
|
+
flickr_attribute :page, :from => '@url'
|
32
32
|
|
33
33
|
find_all :by_photo_id, :call => 'photos.getSizes', :path => 'sizes/size'
|
34
34
|
|
@@ -41,8 +41,8 @@ module Fleakr
|
|
41
41
|
# directory, the file will be created with the original filename from Flickr.
|
42
42
|
# If the target is a file, it will be saved with the specified name. In the
|
43
43
|
# case that the target file already exists, this method will overwrite it.
|
44
|
-
def save_to(target)
|
45
|
-
destination = File.directory?(target) ? "#{target}/#{self.filename}" : "#{target}"
|
44
|
+
def save_to(target, prefix = nil)
|
45
|
+
destination = File.directory?(target) ? "#{target}/#{prefix}#{self.filename}" : "#{target}"
|
46
46
|
File.open(destination, 'w') {|f| f << Net::HTTP.get(URI.parse(self.url)) }
|
47
47
|
end
|
48
48
|
|
data/lib/fleakr/objects/photo.rb
CHANGED
@@ -7,6 +7,10 @@ module Fleakr
|
|
7
7
|
#
|
8
8
|
# [id] The ID for this photo
|
9
9
|
# [title] The title of this photo
|
10
|
+
# [description] The description of this photo
|
11
|
+
# [secret] This photo's secret (used for sharing photo without permissions checking)
|
12
|
+
# [comment_count] Count of the comments attached to this photo
|
13
|
+
# [url] This photo's page on Flickr
|
10
14
|
# [square] The tiny square representation of this photo
|
11
15
|
# [thumbnail] The thumbnail for this photo
|
12
16
|
# [small] The small representation of this photo
|
@@ -25,18 +29,78 @@ module Fleakr
|
|
25
29
|
|
26
30
|
include Fleakr::Support::Object
|
27
31
|
|
28
|
-
flickr_attribute :
|
29
|
-
flickr_attribute :
|
30
|
-
flickr_attribute :
|
31
|
-
flickr_attribute :
|
32
|
-
flickr_attribute :
|
32
|
+
flickr_attribute :id, :from => ['@id', 'photoid']
|
33
|
+
flickr_attribute :title
|
34
|
+
flickr_attribute :description
|
35
|
+
flickr_attribute :farm_id, :from => '@farm'
|
36
|
+
flickr_attribute :server_id, :from => '@server'
|
37
|
+
flickr_attribute :secret
|
38
|
+
flickr_attribute :posted
|
39
|
+
flickr_attribute :taken
|
40
|
+
flickr_attribute :updated, :from => '@lastupdate'
|
41
|
+
flickr_attribute :comment_count, :from => 'comments'
|
42
|
+
flickr_attribute :url
|
43
|
+
|
44
|
+
# TODO:
|
45
|
+
# * owner (user)
|
46
|
+
# * visibility
|
47
|
+
# * editability
|
48
|
+
# * usage
|
49
|
+
# * notes
|
50
|
+
# * tags
|
33
51
|
|
34
52
|
find_all :by_photoset_id, :call => 'photosets.getPhotos', :path => 'photoset/photo'
|
35
53
|
find_all :by_user_id, :call => 'people.getPublicPhotos', :path => 'photos/photo'
|
36
54
|
find_all :by_group_id, :call => 'groups.pools.getPhotos', :path => 'photos/photo'
|
37
55
|
|
56
|
+
find_one :by_id, :using => :photo_id, :call => 'photos.getInfo', :authenticate? => true
|
57
|
+
|
58
|
+
lazily_load :posted, :taken, :updated, :comment_count, :url, :description, :with => :load_info
|
59
|
+
|
38
60
|
has_many :images
|
39
61
|
|
62
|
+
# Upload the photo specified by <tt>filename</tt> to the user's Flickr account. This
|
63
|
+
# call requires authentication.
|
64
|
+
#
|
65
|
+
def self.upload(filename)
|
66
|
+
response = Fleakr::Api::UploadRequest.with_response!(filename)
|
67
|
+
photo = Photo.new(response.body)
|
68
|
+
Photo.find_by_id(photo.id, :authenticate? => true)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Replace the current photo's image with the one specified by filename. This
|
72
|
+
# call requires authentication.
|
73
|
+
#
|
74
|
+
def replace_with(filename)
|
75
|
+
response = Fleakr::Api::UploadRequest.with_response!(filename, :photo_id => self.id, :type => :update)
|
76
|
+
self.populate_from(response.body)
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
# TODO: Refactor this to remove duplication w/ User#load_info - possibly in the lazily_load class method
|
81
|
+
def load_info # :nodoc:
|
82
|
+
response = Fleakr::Api::MethodRequest.with_response!('photos.getInfo', :photo_id => self.id)
|
83
|
+
self.populate_from(response.body)
|
84
|
+
end
|
85
|
+
|
86
|
+
# When was this photo posted?
|
87
|
+
#
|
88
|
+
def posted_at
|
89
|
+
Time.at(posted.to_i)
|
90
|
+
end
|
91
|
+
|
92
|
+
# When was this photo taken?
|
93
|
+
#
|
94
|
+
def taken_at
|
95
|
+
Time.parse(taken)
|
96
|
+
end
|
97
|
+
|
98
|
+
# When was this photo last updated? This includes addition of tags and other metadata.
|
99
|
+
#
|
100
|
+
def updated_at
|
101
|
+
Time.at(updated.to_i)
|
102
|
+
end
|
103
|
+
|
40
104
|
# Create methods to access image sizes by name
|
41
105
|
SIZES.each do |size|
|
42
106
|
define_method(size) do
|