sailthru-client 1.01
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.md +4 -0
- data/lib/sailthru.rb +374 -0
- metadata +81 -0
data/README.md
ADDED
data/lib/sailthru.rb
ADDED
@@ -0,0 +1,374 @@
|
|
1
|
+
################################################################################
|
2
|
+
#
|
3
|
+
# A simple client library to remotely access the Sailthru REST API.
|
4
|
+
#
|
5
|
+
################################################################################
|
6
|
+
#
|
7
|
+
# Copyright (c) 2007 Sailthru, Inc.
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Special thanks to the iminlikewithyou.com team for the development
|
11
|
+
# of this library.
|
12
|
+
#
|
13
|
+
# Redistribution and use in source and binary forms, with or without
|
14
|
+
# modification, are permitted provided that the following conditions
|
15
|
+
# are met:
|
16
|
+
#
|
17
|
+
# 1. Redistributions of source code must retain the above copyright
|
18
|
+
# notice, this list of conditions and the following disclaimer.
|
19
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
20
|
+
# notice, this list of conditions and the following disclaimer in the
|
21
|
+
# documentation and/or other materials provided with the distribution.
|
22
|
+
#
|
23
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
24
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
25
|
+
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
26
|
+
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
27
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
28
|
+
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
29
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
30
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
31
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
32
|
+
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
#
|
34
|
+
################################################################################
|
35
|
+
|
36
|
+
require 'net/http'
|
37
|
+
require 'uri'
|
38
|
+
require 'cgi'
|
39
|
+
require 'rubygems'
|
40
|
+
require 'json'
|
41
|
+
require 'md5'
|
42
|
+
|
43
|
+
class SailthruClientException < Exception
|
44
|
+
end
|
45
|
+
|
46
|
+
class SailthruClient
|
47
|
+
attr_accessor :api_uri, :api_key, :secret, :version, :last_request
|
48
|
+
|
49
|
+
VERSION = '1.01'
|
50
|
+
|
51
|
+
# params:
|
52
|
+
# api_key, String
|
53
|
+
# secret, String
|
54
|
+
# api_uri, String
|
55
|
+
#
|
56
|
+
# Instantiate a new client; constructor optionally takes overrides for key/secret/uri.
|
57
|
+
def initialize(api_key, secret, api_uri)
|
58
|
+
@api_key = api_key
|
59
|
+
@secret = secret
|
60
|
+
@api_uri = api_uri
|
61
|
+
end
|
62
|
+
|
63
|
+
# params:
|
64
|
+
# template_name, String
|
65
|
+
# email, String
|
66
|
+
# replacements, Hash
|
67
|
+
# options, Hash
|
68
|
+
# replyto: override Reply-To header
|
69
|
+
# test: send as test email (subject line will be marked, will not count towards stats)
|
70
|
+
# returns:
|
71
|
+
# Hash, response data from server
|
72
|
+
def send(template_name, email, vars, options = {}, schedule_time = nil)
|
73
|
+
post = {}
|
74
|
+
post[:template] = template_name
|
75
|
+
post[:email] = email
|
76
|
+
post[:vars] = vars
|
77
|
+
post[:options] = options
|
78
|
+
if schedule_time != nil
|
79
|
+
post[:schedule_time] = schedule_time
|
80
|
+
end
|
81
|
+
return self.api_post(:send, post)
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
# params:
|
86
|
+
# send_id, Fixnum
|
87
|
+
# returns:
|
88
|
+
# Hash, response data from server
|
89
|
+
#
|
90
|
+
# Get the status of a send.
|
91
|
+
def get_send(send_id)
|
92
|
+
self.api_get(:send, {:send_id => send_id.to_s})
|
93
|
+
end
|
94
|
+
|
95
|
+
# params:
|
96
|
+
# name, String
|
97
|
+
# list, String
|
98
|
+
# schedule_time, String
|
99
|
+
# from_name, String
|
100
|
+
# from_email, String
|
101
|
+
# subject, String
|
102
|
+
# content_html, String
|
103
|
+
# content_text, String
|
104
|
+
# options, Hash
|
105
|
+
# returns:
|
106
|
+
# Hash, response data from server
|
107
|
+
#
|
108
|
+
# Schedule a mass mail blast
|
109
|
+
def schedule_blast(name, list, schedule_time, from_name, from_email, subject, content_html, content_text, options)
|
110
|
+
post = options ? options : {}
|
111
|
+
post[:name] = name
|
112
|
+
post[:list] = list
|
113
|
+
post[:schedule_time] = schedule_time
|
114
|
+
post[:from_name] = from_name
|
115
|
+
post[:from_email] = from_email
|
116
|
+
post[:subject] = subject
|
117
|
+
post[:content_html] = content_html
|
118
|
+
post[:content_text] = content_text
|
119
|
+
self.api_post(:blast, post)
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
# params:
|
124
|
+
# blast_id, Fixnum
|
125
|
+
# returns:
|
126
|
+
# Hash, response data from server
|
127
|
+
#
|
128
|
+
# Get information on a previously scheduled email blast
|
129
|
+
def get_blast(blast_id)
|
130
|
+
self.api_get(:blast, {:blast_id => blast_id.to_s})
|
131
|
+
end
|
132
|
+
|
133
|
+
# params:
|
134
|
+
# email, String
|
135
|
+
# returns:
|
136
|
+
# Hash, response data from server
|
137
|
+
#
|
138
|
+
# Return information about an email address, including replacement vars and lists.
|
139
|
+
def get_email(email)
|
140
|
+
self.api_get(:email, {:email => email})
|
141
|
+
end
|
142
|
+
|
143
|
+
# params:
|
144
|
+
# email, String
|
145
|
+
# vars, Hash
|
146
|
+
# lists, Hash mapping list name => 1 for subscribed, 0 for unsubscribed
|
147
|
+
# returns:
|
148
|
+
# Hash, response data from server
|
149
|
+
#
|
150
|
+
# Set replacement vars and/or list subscriptions for an email address.
|
151
|
+
def set_email(email, vars = {}, lists = {}, templates = {})
|
152
|
+
data = {:email => email}
|
153
|
+
data[:vars] = vars unless vars.empty?
|
154
|
+
data[:lists] = lists unless lists.empty?
|
155
|
+
data[:templates] = templates unless templates.empty?
|
156
|
+
self.api_post(:email, data)
|
157
|
+
end
|
158
|
+
|
159
|
+
# params:
|
160
|
+
# email, String
|
161
|
+
# password, String
|
162
|
+
# with_names, Boolean
|
163
|
+
# returns:
|
164
|
+
# Hash, response data from server
|
165
|
+
#
|
166
|
+
# Fetch email contacts from an address book at one of the major email providers (aol/gmail/hotmail/yahoo)
|
167
|
+
# Use the with_names parameter if you want to fetch the contact names as well as emails
|
168
|
+
def import_contacts(email, password, with_names = false)
|
169
|
+
data = { :email => email, :password => password }
|
170
|
+
data[:names] = 1 if with_names
|
171
|
+
self.api_post(:contacts, data)
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
# params:
|
176
|
+
# template_name, String
|
177
|
+
# returns:
|
178
|
+
# Hash of response data.
|
179
|
+
#
|
180
|
+
# Get a template.
|
181
|
+
def get_template(template_name)
|
182
|
+
self.api_get(:template, {:template => template_name})
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
# params:
|
187
|
+
# template_name, String
|
188
|
+
# template_fields, Hash
|
189
|
+
# returns:
|
190
|
+
# Hash containg response from the server.
|
191
|
+
#
|
192
|
+
# Save a template.
|
193
|
+
def save_template(template_name, template_fields)
|
194
|
+
data = template_fields
|
195
|
+
data[:template] = template_name
|
196
|
+
self.api_post(:template, data)
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
# params:
|
201
|
+
# params, Hash
|
202
|
+
# request, String
|
203
|
+
# returns:
|
204
|
+
# TrueClass or FalseClass, Returns true if the incoming request is an authenticated verify post.
|
205
|
+
def receive_verify_post(params, request)
|
206
|
+
if request.post?
|
207
|
+
[:action, :email, :send_id, :sig].each { |key| return false unless params.has_key?(key) }
|
208
|
+
|
209
|
+
return false unless params[:action] == :verify
|
210
|
+
|
211
|
+
sig = params[:sig]
|
212
|
+
params.delete(:sig)
|
213
|
+
return false unless sig == SailthruClient.get_signature_hash(params, @secret)
|
214
|
+
|
215
|
+
_send = self.get_send(params[:send_id])
|
216
|
+
return false unless _send.has_key?(:email)
|
217
|
+
|
218
|
+
return false unless _send[:email] == params[:email]
|
219
|
+
|
220
|
+
return true
|
221
|
+
else
|
222
|
+
return false
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
# Perform API GET request
|
228
|
+
def api_get(action, data)
|
229
|
+
api_request(action, data, 'GET')
|
230
|
+
end
|
231
|
+
|
232
|
+
# Perform API POST request
|
233
|
+
def api_post(action, data)
|
234
|
+
api_request(action, data, 'POST')
|
235
|
+
end
|
236
|
+
|
237
|
+
# params:
|
238
|
+
# action, String
|
239
|
+
# data, Hash
|
240
|
+
# request, String "GET" or "POST"
|
241
|
+
# returns:
|
242
|
+
# Hash
|
243
|
+
#
|
244
|
+
# Perform an API request, using the shared-secret auth hash.
|
245
|
+
#
|
246
|
+
def api_request(action, data, request_type)
|
247
|
+
data[:api_key] = @api_key
|
248
|
+
data[:format] ||= 'json'
|
249
|
+
data[:sig] = SailthruClient.get_signature_hash(data, @secret)
|
250
|
+
_result = self.http_request("#{@api_uri}/#{action}", data, request_type)
|
251
|
+
|
252
|
+
|
253
|
+
# NOTE: don't do the unserialize here
|
254
|
+
unserialized = JSON.parse(_result)
|
255
|
+
return unserialized ? unserialized : _result
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
# params:
|
260
|
+
# uri, String
|
261
|
+
# data, Hash
|
262
|
+
# method, String "GET" or "POST"
|
263
|
+
# returns:
|
264
|
+
# String, body of response
|
265
|
+
def http_request(uri, data, method = 'POST')
|
266
|
+
data = flatten_nested_hash(data, false)
|
267
|
+
# puts data.inspect
|
268
|
+
if method == 'POST'
|
269
|
+
post_data = data
|
270
|
+
else
|
271
|
+
uri += "?" + data.map{ |key, value| "#{CGI::escape(key)}=#{CGI::escape(value)}" }.join("&")
|
272
|
+
end
|
273
|
+
req = nil
|
274
|
+
headers = {"User-Agent" => "Sailthru API Ruby Client #{VERSION}"}
|
275
|
+
|
276
|
+
_uri = URI.parse(uri)
|
277
|
+
if method == 'POST'
|
278
|
+
req = Net::HTTP::Post.new(_uri.path, headers)
|
279
|
+
req.set_form_data(data)
|
280
|
+
else
|
281
|
+
req = Net::HTTP::Get.new("#{_uri.path}?#{_uri.query}", headers)
|
282
|
+
end
|
283
|
+
|
284
|
+
@last_request = req
|
285
|
+
begin
|
286
|
+
response = Net::HTTP.start(_uri.host, _uri.port) {|http|
|
287
|
+
http.request(req)
|
288
|
+
}
|
289
|
+
rescue Exception => e
|
290
|
+
raise SailthruClientException.new("Unable to open stream: #{_uri.to_s}");
|
291
|
+
end
|
292
|
+
|
293
|
+
if response.body
|
294
|
+
return response.body
|
295
|
+
else
|
296
|
+
raise SailthruClientException.new("No response received from stream: #{_uri.to_s}")
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
# Flatten nested hash for GET / POST request.
|
302
|
+
def flatten_nested_hash(hash, brackets = true)
|
303
|
+
f = {}
|
304
|
+
hash.each do |key, value|
|
305
|
+
_key = brackets ? "[#{key}]" : key.to_s
|
306
|
+
if value.class == Hash
|
307
|
+
flatten_nested_hash(value).each do |k, v|
|
308
|
+
f["#{_key}#{k}"] = v
|
309
|
+
end
|
310
|
+
elsif value.class == Array
|
311
|
+
temp_hash = Hash.new()
|
312
|
+
value.each_with_index do |v, i|
|
313
|
+
temp_hash[i.to_s] = v
|
314
|
+
end
|
315
|
+
flatten_nested_hash(temp_hash).each do |k, v|
|
316
|
+
f["#{_key}#{k}"] = v
|
317
|
+
end
|
318
|
+
|
319
|
+
else
|
320
|
+
f[_key] = value
|
321
|
+
end
|
322
|
+
end
|
323
|
+
return f
|
324
|
+
end
|
325
|
+
|
326
|
+
# params:
|
327
|
+
# params, Hash
|
328
|
+
# returns:
|
329
|
+
# Array, values of each item in the Hash (and nested hashes)
|
330
|
+
#
|
331
|
+
# Extracts the values of a set of parameters, recursing into nested assoc arrays.
|
332
|
+
def self.extract_param_values(params)
|
333
|
+
values = []
|
334
|
+
params.each do |k, v|
|
335
|
+
# puts "k,v: #{k}, #{v}"
|
336
|
+
if v.class == Hash
|
337
|
+
values.concat SailthruClient.extract_param_values(v)
|
338
|
+
elsif v.class == Array
|
339
|
+
temp_hash = Hash.new()
|
340
|
+
v.each_with_index do |v_,i_|
|
341
|
+
temp_hash[i_.to_s] = v_
|
342
|
+
end
|
343
|
+
values.concat self.extract_param_values(temp_hash)
|
344
|
+
else
|
345
|
+
values.push v.to_s()
|
346
|
+
end
|
347
|
+
end
|
348
|
+
return values
|
349
|
+
end
|
350
|
+
|
351
|
+
# params:
|
352
|
+
# params, Hash
|
353
|
+
# secret, String
|
354
|
+
# returns:
|
355
|
+
# String
|
356
|
+
#
|
357
|
+
# Returns the unhashed signature string (secret + sorted list of param values) for an API call.
|
358
|
+
def self.get_signature_string(params, secret)
|
359
|
+
return secret + self.extract_param_values(params).sort.join("")
|
360
|
+
end
|
361
|
+
|
362
|
+
|
363
|
+
# params:
|
364
|
+
# params, Hash
|
365
|
+
# secret, String
|
366
|
+
# returns:
|
367
|
+
# String
|
368
|
+
#
|
369
|
+
# Returns an MD5 hash of the signature string for an API call.
|
370
|
+
def self.get_signature_hash(params, secret)
|
371
|
+
return MD5.md5(self.get_signature_string(params, secret)).to_s # debuggin
|
372
|
+
end
|
373
|
+
|
374
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sailthru-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 13
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: "1.01"
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Prajwal Tuladhar
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-01-19 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: json
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
description:
|
35
|
+
email: praj@sailthru.com
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- README.md
|
42
|
+
files:
|
43
|
+
- README.md
|
44
|
+
- lib/sailthru.rb
|
45
|
+
has_rdoc: true
|
46
|
+
homepage: http://docs.sailthru.com
|
47
|
+
licenses: []
|
48
|
+
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options:
|
51
|
+
- --main
|
52
|
+
- README.md
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 3
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.4.1
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: A simple client library to remotely access the Sailthru REST API.
|
80
|
+
test_files: []
|
81
|
+
|