sailthru-client 1.01

Sign up to get free protection for your applications and to get access to all the features.
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
+