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.
Files changed (3) hide show
  1. data/README.md +4 -0
  2. data/lib/sailthru.rb +374 -0
  3. metadata +81 -0
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ # A simple client library to remotely access the Sailthru REST API.
2
+
3
+ ## Installing (Tested with Ruby 1.8.7)
4
+ $ gem install sailthru
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
+