postful 0.1.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/doc/classes/Postful/AuthorizationException.html +111 -0
- data/doc/classes/Postful/Base.html +255 -0
- data/doc/classes/Postful/Base.src/M000001.html +21 -0
- data/doc/classes/Postful/Base.src/M000002.html +23 -0
- data/doc/classes/Postful/Base.src/M000003.html +22 -0
- data/doc/classes/Postful/Base.src/M000004.html +22 -0
- data/doc/classes/Postful/Base.src/M000005.html +20 -0
- data/doc/classes/Postful/Base.src/M000006.html +18 -0
- data/doc/classes/Postful/InternalServerError.html +111 -0
- data/doc/classes/Postful/Letter.html +347 -0
- data/doc/classes/Postful/Letter.src/M000015.html +18 -0
- data/doc/classes/Postful/Letter.src/M000016.html +18 -0
- data/doc/classes/Postful/Letter.src/M000017.html +18 -0
- data/doc/classes/Postful/Letter.src/M000018.html +18 -0
- data/doc/classes/Postful/Letter.src/M000019.html +18 -0
- data/doc/classes/Postful/Letter.src/M000020.html +18 -0
- data/doc/classes/Postful/Letter.src/M000021.html +18 -0
- data/doc/classes/Postful/Letter.src/M000022.html +18 -0
- data/doc/classes/Postful/Letter.src/M000023.html +18 -0
- data/doc/classes/Postful/Letter.src/M000024.html +18 -0
- data/doc/classes/Postful/Letter.src/M000025.html +18 -0
- data/doc/classes/Postful/Letter.src/M000026.html +18 -0
- data/doc/classes/Postful/NotFoundException.html +111 -0
- data/doc/classes/Postful/Order.html +276 -0
- data/doc/classes/Postful/Order.src/M000007.html +19 -0
- data/doc/classes/Postful/Order.src/M000008.html +24 -0
- data/doc/classes/Postful/Order.src/M000009.html +26 -0
- data/doc/classes/Postful/Order.src/M000010.html +18 -0
- data/doc/classes/Postful/Service.html +191 -0
- data/doc/classes/Postful/Service.src/M000011.html +19 -0
- data/doc/classes/Postful/Service.src/M000012.html +24 -0
- data/doc/classes/Postful/Service.src/M000013.html +19 -0
- data/doc/classes/Postful/Service.src/M000014.html +24 -0
- data/doc/classes/Postful/UnknownResponseException.html +111 -0
- data/doc/classes/Postful/ValidationException.html +111 -0
- data/doc/created.rid +1 -0
- data/doc/files/MIT-LICENSE.html +129 -0
- data/doc/files/README.html +164 -0
- data/doc/files/lib/postful/base_rb.html +108 -0
- data/doc/files/lib/postful/instance_exec_module_rb.html +101 -0
- data/doc/files/lib/postful/letter_rb.html +109 -0
- data/doc/files/lib/postful/order_rb.html +110 -0
- data/doc/files/lib/postful/service_rb.html +109 -0
- data/doc/files/lib/postful/util_rb.html +101 -0
- data/doc/files/lib/postful_rb.html +110 -0
- data/doc/fr_class_index.html +35 -0
- data/doc/fr_file_index.html +35 -0
- data/doc/fr_method_index.html +52 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/lib/postful.rb +3 -0
- data/lib/postful/base.rb +163 -0
- data/lib/postful/instance_exec_module.rb +25 -0
- data/lib/postful/letter.rb +134 -0
- data/lib/postful/order.rb +130 -0
- data/lib/postful/service.rb +40 -0
- data/lib/postful/util.rb +69 -0
- data/test/suite.rb +5 -0
- data/test/test_base.rb +131 -0
- data/test/test_letter.rb +115 -0
- data/test/test_order.rb +49 -0
- data/test/test_postcard.rb +12 -0
- data/test/test_service.rb +52 -0
- metadata +128 -0
data/lib/postful.rb
ADDED
data/lib/postful/base.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'builder'
|
2
|
+
require File.join(File.dirname(__FILE__), 'order')
|
3
|
+
require File.join(File.dirname(__FILE__), 'util')
|
4
|
+
|
5
|
+
module Postful #:nodoc:
|
6
|
+
class AuthorizationException < StandardError; end
|
7
|
+
class ValidationException < StandardError; end
|
8
|
+
class UnknownResponseException < StandardError; end
|
9
|
+
class NotFoundException < StandardError; end
|
10
|
+
class InternalServerError < StandardError; end
|
11
|
+
|
12
|
+
#--
|
13
|
+
# TODO: address validations and SimpleValidation
|
14
|
+
#++
|
15
|
+
class Base
|
16
|
+
attr_accessor :tag1
|
17
|
+
attr_accessor :tag2
|
18
|
+
attr_accessor :tag3
|
19
|
+
attr_accessor :return_address
|
20
|
+
|
21
|
+
include Util
|
22
|
+
|
23
|
+
def initialize(email, password)
|
24
|
+
@email = email
|
25
|
+
@password = password
|
26
|
+
@documents = []
|
27
|
+
@addressees = []
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add a domestic recipient to a mailing
|
31
|
+
def add_address(attributes = {})
|
32
|
+
returning DomesticAddress.new do |address|
|
33
|
+
[:name, :company, :address, :address2, :city, :state, :postal_code].each do |field|
|
34
|
+
address.send("#{field}=", attributes[field] || attributes[field.to_s])
|
35
|
+
end
|
36
|
+
@addressees << address
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add an international recipient to a mailing
|
41
|
+
def add_international_address(lines, country)
|
42
|
+
returning InternationalAddress.new do |address|
|
43
|
+
address.lines = lines
|
44
|
+
address.country = country
|
45
|
+
@addressees << address
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def mail!
|
50
|
+
if !valid?
|
51
|
+
raise errors.join("\n")
|
52
|
+
else
|
53
|
+
mail_without_validation!
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def valid?
|
58
|
+
errors.clear
|
59
|
+
validate
|
60
|
+
errors.empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
def errors
|
64
|
+
@errors ||= []
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def build_request #:nodoc:
|
70
|
+
request = ''
|
71
|
+
xml = Builder::XmlMarkup.new(:target => request)
|
72
|
+
xml.mail do
|
73
|
+
xml.documents do
|
74
|
+
@documents.each do |document|
|
75
|
+
document.build_request(xml)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
xml.addressees do
|
79
|
+
@addressees.each do |addressee|
|
80
|
+
addressee.build_request(xml)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
xml.tag1 tag1 if tag1
|
84
|
+
xml.tag2 tag2 if tag2
|
85
|
+
xml.tag3 tag3 if tag3
|
86
|
+
if return_address
|
87
|
+
xml.return_address do
|
88
|
+
return_address.each_with_index do |line, i|
|
89
|
+
xml.tag! "line#{i + 1}", line
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
request
|
95
|
+
end
|
96
|
+
|
97
|
+
def validate #:nodoc:
|
98
|
+
errors << 'Requires at least one addressee' if @addressees.empty?
|
99
|
+
end
|
100
|
+
|
101
|
+
def mail_without_validation! #:nodoc:
|
102
|
+
upload_documents
|
103
|
+
xml = build_request
|
104
|
+
send_request(xml)
|
105
|
+
end
|
106
|
+
|
107
|
+
def send_request(xml) #:nodoc:
|
108
|
+
response = post_request_on_path('/service/mail', xml, @email, @password)
|
109
|
+
case response.code
|
110
|
+
when "201" then
|
111
|
+
Postful::Order.build_from_response(response.body, @email, @password)
|
112
|
+
when "422" then
|
113
|
+
raise Postful::ValidationException, build_error_message_from_response(response.body)
|
114
|
+
else
|
115
|
+
process_standard_responses(response)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
class DomesticAddress #:nodoc:
|
122
|
+
attr_accessor :name
|
123
|
+
attr_accessor :company
|
124
|
+
attr_accessor :address
|
125
|
+
attr_accessor :address2
|
126
|
+
attr_accessor :city
|
127
|
+
attr_accessor :state
|
128
|
+
attr_accessor :postal_code
|
129
|
+
|
130
|
+
def build_request(builder)
|
131
|
+
builder.addressee do
|
132
|
+
builder.name name
|
133
|
+
builder.company company if company
|
134
|
+
builder.address address
|
135
|
+
builder.address2 address2 if address2
|
136
|
+
builder.city city
|
137
|
+
builder.state state
|
138
|
+
builder.postal_code postal_code
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class InternationalAddress #:nodoc:
|
144
|
+
attr_accessor :country
|
145
|
+
attr_accessor :lines
|
146
|
+
|
147
|
+
def build_request(builder)
|
148
|
+
builder.addressee do
|
149
|
+
lines.each_with_index do |line, i|
|
150
|
+
builder.tag! "line#{i + 1}", line
|
151
|
+
end
|
152
|
+
builder.country country
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def returning(value)
|
158
|
+
yield(value)
|
159
|
+
value
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module InstanceExecModule #:nodoc:
|
2
|
+
unless defined? instance_exec # 1.9
|
3
|
+
module InstanceExecMethods #:nodoc:
|
4
|
+
end
|
5
|
+
include InstanceExecMethods
|
6
|
+
|
7
|
+
# From Mauricio's http://eigenclass.org/hiki/bounded+space+instance_exec
|
8
|
+
def instance_exec(*args, &block)
|
9
|
+
begin
|
10
|
+
old_critical, Thread.critical = Thread.critical, true
|
11
|
+
n = 0
|
12
|
+
n += 1 while respond_to?(method_name = "__instance_exec#{n}")
|
13
|
+
InstanceExecMethods.module_eval { define_method(method_name, &block) }
|
14
|
+
ensure
|
15
|
+
Thread.critical = old_critical
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
send(method_name, *args)
|
20
|
+
ensure
|
21
|
+
InstanceExecMethods.module_eval { remove_method(method_name) } rescue nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'builder'
|
3
|
+
require File.join(File.dirname(__FILE__), 'base')
|
4
|
+
require File.join(File.dirname(__FILE__), 'util')
|
5
|
+
|
6
|
+
module Postful
|
7
|
+
# Builder for mailing letters with the Postful Web Service.
|
8
|
+
|
9
|
+
class Letter < Base
|
10
|
+
# Add an HTML document to the request
|
11
|
+
def add_html_document(text)
|
12
|
+
add_document('html', text)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Add an HTML document to the request
|
16
|
+
def add_html_document_from_file(filename)
|
17
|
+
add_document_from_file('html', filename)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add a Microsoft Word document to the request
|
21
|
+
def add_word_document(text)
|
22
|
+
add_document('doc', text)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Add a Microsoft Word document to the request
|
26
|
+
def add_word_document_from_file(filename)
|
27
|
+
add_document_from_file('doc', filename)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add a Rich Text Format document to the request
|
31
|
+
def add_rtf_document(text)
|
32
|
+
add_document('rtf', text)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add a Rich Text Format document to the request
|
36
|
+
def add_rtf_document_from_file(filename)
|
37
|
+
add_document_from_file('rtf', filename)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add an Open Office document to the request
|
41
|
+
def add_open_office_document(text)
|
42
|
+
add_document('odt', text)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Add an Open Office document to the request
|
46
|
+
def add_open_office_document_from_file(filename)
|
47
|
+
add_document_from_file('odt', filename)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add a text document to the request
|
51
|
+
def add_text_document(text)
|
52
|
+
add_document('txt', text)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Add a text document to the request
|
56
|
+
def add_text_document_from_file(filename)
|
57
|
+
add_document_from_file('txt', filename)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Add a PDF document to the request
|
61
|
+
def add_pdf_document(text)
|
62
|
+
add_document('pdf', text)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Add a PDF document to the request
|
66
|
+
def add_pdf_document_from_file(filename)
|
67
|
+
add_document_from_file('pdf', filename)
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def validate #:nodoc:
|
73
|
+
super
|
74
|
+
errors << 'Requires at least one document' if @documents.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def upload_documents
|
80
|
+
@documents.each do |document|
|
81
|
+
document.upload
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def add_document(format, content)
|
86
|
+
returning Document.new(@email, @password) do |document|
|
87
|
+
document.format = format
|
88
|
+
document.content = content
|
89
|
+
@documents << document
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_document_from_file(format, filename)
|
94
|
+
content = open(filename, "rb") do |io|
|
95
|
+
io.read
|
96
|
+
end
|
97
|
+
add_document(format, content)
|
98
|
+
end
|
99
|
+
|
100
|
+
class Document #:nodoc:
|
101
|
+
include Util
|
102
|
+
|
103
|
+
attr_accessor :format
|
104
|
+
attr_accessor :content
|
105
|
+
attr_accessor :attachment_name
|
106
|
+
|
107
|
+
def initialize(email, password)
|
108
|
+
@email = email
|
109
|
+
@password = password
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_request(builder)
|
113
|
+
builder.document do
|
114
|
+
builder.type format
|
115
|
+
builder.attachment attachment_name
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def upload
|
120
|
+
headers = {
|
121
|
+
'Content-Type' => 'application/octet-stream',
|
122
|
+
'Content-Length' => content.size.to_s
|
123
|
+
}
|
124
|
+
response = post_request_on_path('/service/upload', content, @email, @password, headers)
|
125
|
+
if response.is_a?(Net::HTTPOK) && response.body =~ /<id>(.*)<\/id>/
|
126
|
+
self.attachment_name = $1
|
127
|
+
else
|
128
|
+
# raise stuff
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'date'
|
4
|
+
require File.join(File.dirname(__FILE__), 'instance_exec_module')
|
5
|
+
require File.join(File.dirname(__FILE__), 'util')
|
6
|
+
|
7
|
+
module Postful
|
8
|
+
# Represent a Postful order.
|
9
|
+
class Order
|
10
|
+
attr :id
|
11
|
+
attr :status
|
12
|
+
attr :source
|
13
|
+
attr :pages
|
14
|
+
attr :preview_url
|
15
|
+
attr :price
|
16
|
+
attr :tag1
|
17
|
+
attr :tag2
|
18
|
+
attr :tag3
|
19
|
+
attr :history
|
20
|
+
attr :addressees
|
21
|
+
attr :warnings
|
22
|
+
attr :return_address
|
23
|
+
attr :errors
|
24
|
+
|
25
|
+
include Util
|
26
|
+
|
27
|
+
def preview
|
28
|
+
url = URI.parse(preview_url)
|
29
|
+
url.read
|
30
|
+
end
|
31
|
+
|
32
|
+
def refresh
|
33
|
+
response = get_request_on_path("/service/mail/#{id}", @email, @password)
|
34
|
+
case response.code
|
35
|
+
when "200" then
|
36
|
+
build_from_response(response.body)
|
37
|
+
else
|
38
|
+
process_standard_responses(response)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def cancel
|
43
|
+
response = delete_request_on_path("/service/mail/#{id}", @email, @password)
|
44
|
+
case response.code
|
45
|
+
when "200" then
|
46
|
+
build_from_response(response.body)
|
47
|
+
when "422" then
|
48
|
+
raise Postful::ValidationException, build_error_message_from_response(response.body)
|
49
|
+
else
|
50
|
+
process_standard_responses(response)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def self.build_all_from_response(response, email, password)
|
57
|
+
|
58
|
+
document = REXML::Document.new response
|
59
|
+
root = document.root
|
60
|
+
results = []
|
61
|
+
root.elements.each("mail") do |mail|
|
62
|
+
results << new do
|
63
|
+
@email = email
|
64
|
+
@password = password
|
65
|
+
build_from_root(mail)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
results
|
69
|
+
end
|
70
|
+
|
71
|
+
def build_from_root(root)
|
72
|
+
@id = root.elements['id'].text.to_i
|
73
|
+
@status = root.elements['status'].text
|
74
|
+
@source = root.elements['source'].text
|
75
|
+
@pages = root.elements['pages'].text.to_i
|
76
|
+
@preview_url = root.elements['preview'].text
|
77
|
+
@price = root.elements['price'].text
|
78
|
+
@tag1 = root.elements['tag1'].text
|
79
|
+
@tag2 = root.elements['tag2'].text
|
80
|
+
@tag3 = root.elements['tag3'].text
|
81
|
+
@history = []
|
82
|
+
root.elements.each("history/status") do |element|
|
83
|
+
status = []
|
84
|
+
status << Time.parse(element.elements['at'].text)
|
85
|
+
status << element.elements['message'].text
|
86
|
+
@history << status
|
87
|
+
end
|
88
|
+
@warnings = []
|
89
|
+
root.elements.each("warnings/warning") do |element|
|
90
|
+
@warnings << element.elements['message'].text
|
91
|
+
end
|
92
|
+
@return_address = []
|
93
|
+
root.elements["return-address"].elements.each do |element|
|
94
|
+
@return_address << element.text
|
95
|
+
end
|
96
|
+
@addressees = []
|
97
|
+
root.elements["addressees"].elements.each do |addressee|
|
98
|
+
address = []
|
99
|
+
addressee.elements.each do |line|
|
100
|
+
address << line.text
|
101
|
+
end
|
102
|
+
@addressees << address
|
103
|
+
end
|
104
|
+
@errors = []
|
105
|
+
root.elements.each("fatal-errors/fatal-error") do |element|
|
106
|
+
@errors << element.elements['message'].text
|
107
|
+
end
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_from_response(response)
|
112
|
+
document = REXML::Document.new response
|
113
|
+
build_from_root(document.root)
|
114
|
+
end
|
115
|
+
|
116
|
+
def initialize(&initializer)
|
117
|
+
instance_exec &initializer
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.build_from_response(response, email, password)
|
121
|
+
o = new do
|
122
|
+
@email = email
|
123
|
+
@password = password
|
124
|
+
build_from_response(response)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
include InstanceExecModule
|
129
|
+
end
|
130
|
+
end
|