reagent-fleakr 0.3.0 → 0.4.0
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/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
|