jung 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +10 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +11 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/jung.gemspec +29 -0
- data/lib/jung.rb +10 -0
- data/lib/jung/campaign.rb +17 -0
- data/lib/jung/config.rb +22 -0
- data/lib/jung/drivers.rb +7 -0
- data/lib/jung/drivers/infobip.rb +19 -0
- data/lib/jung/drivers/infobip/api.rb +71 -0
- data/lib/jung/drivers/infobip/campaign.rb +76 -0
- data/lib/jung/drivers/infobip/sms_counter.rb +62 -0
- data/lib/jung/drivers/mailchimp.rb +22 -0
- data/lib/jung/drivers/mailchimp/api.rb +210 -0
- data/lib/jung/drivers/mailchimp/campaign.rb +86 -0
- data/lib/jung/drivers/mailchimp/list.rb +40 -0
- data/lib/jung/list.rb +30 -0
- data/lib/jung/recipient.rb +55 -0
- data/lib/jung/sender.rb +12 -0
- data/test/helper.rb +18 -0
- data/test/test_jung_infobip.rb +29 -0
- data/test/test_jung_mailchimp.rb +71 -0
- metadata +133 -0
data/.document
ADDED
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
gem "gibbon"
|
7
|
+
gem "activesupport"
|
8
|
+
gem "i18n"
|
9
|
+
gem "rdoc"
|
10
|
+
|
11
|
+
# Add dependencies to develop your gem here.
|
12
|
+
# Include everything needed to run rake, tests, features, etc.
|
13
|
+
group :development do
|
14
|
+
gem "shoulda", ">= 0"
|
15
|
+
gem "bundler", "~> 1.0.0"
|
16
|
+
gem "jeweler", "~> 1.6.4"
|
17
|
+
gem "rcov", ">= 0"
|
18
|
+
gem "pry"
|
19
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Ariel Patschiki
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
= jung
|
2
|
+
|
3
|
+
Have you ever wondered how it would be if there were an easy way to send your message to billions of people?
|
4
|
+
|
5
|
+
The Jung's collective unconscious will help in this arduous task!
|
6
|
+
|
7
|
+
== Copyright
|
8
|
+
|
9
|
+
Copyright (c) 2011 MobVox. See LICENSE.txt for
|
10
|
+
further details.
|
11
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "jung"
|
18
|
+
gem.homepage = "http://github.com/mobvox/jung"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{TODO: one-line summary of your gem}
|
21
|
+
gem.description = %Q{TODO: longer description of your gem}
|
22
|
+
gem.email = "arielpts@me.com"
|
23
|
+
gem.authors = ["Ariel Patschiki"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
Rcov::RcovTask.new do |test|
|
37
|
+
test.libs << 'test'
|
38
|
+
test.pattern = 'test/**/test_*.rb'
|
39
|
+
test.verbose = true
|
40
|
+
test.rcov_opts << '--exclude "gems/*"'
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rdoc/task'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "jung #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.6.0
|
data/jung.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "jung"
|
6
|
+
s.version = File.read('VERSION')
|
7
|
+
s.authors = ["arielpts", "danxexe"]
|
8
|
+
s.email = ["arielpts@me.com"]
|
9
|
+
s.homepage = "http://www.mobvox.com.br"
|
10
|
+
s.summary = "Eletronic message deliver proxy"
|
11
|
+
s.description = "Have you ever wondered how it would be if there were an easy way to send your message to billions of people? The Jung’s collective unconscious will help in this arduous task!"
|
12
|
+
|
13
|
+
# s.rubyforge_project = "admin_widgets"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
# specify any dependencies here; for example:
|
21
|
+
# s.add_development_dependency "rspec"
|
22
|
+
# s.add_runtime_dependency "rest-client"
|
23
|
+
|
24
|
+
s.add_runtime_dependency "gibbon"
|
25
|
+
s.add_runtime_dependency "activesupport"
|
26
|
+
s.add_runtime_dependency "i18n"
|
27
|
+
s.add_runtime_dependency "rdoc"
|
28
|
+
s.add_runtime_dependency "gsm_encoder"
|
29
|
+
end
|
data/lib/jung.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Jung
|
2
|
+
class Campaign < Jung::List
|
3
|
+
|
4
|
+
attr_reader :id
|
5
|
+
attr_accessor :name, :sender, :subject, :message
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
@name = options[:name]
|
9
|
+
@subject = options[:subject]
|
10
|
+
@sender = options[:sender]
|
11
|
+
@message = options[:message]
|
12
|
+
|
13
|
+
super options
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/lib/jung/config.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Jung
|
2
|
+
class Config
|
3
|
+
|
4
|
+
attr_reader :driver, :options
|
5
|
+
|
6
|
+
def self.load(file = "config/jung.yml", namespace = nil)
|
7
|
+
options = YAML.load_file file
|
8
|
+
return self.new options[namespace.to_s]
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(options)
|
12
|
+
@driver = options["driver"]
|
13
|
+
@options = options["options"]
|
14
|
+
Jung::Drivers.load options["driver"]
|
15
|
+
end
|
16
|
+
|
17
|
+
def driver_const
|
18
|
+
Jung::Drivers.const_get self.driver.camelize
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
data/lib/jung/drivers.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Jung::Drivers::Infobip
|
2
|
+
|
3
|
+
require 'jung/drivers/infobip/api.rb'
|
4
|
+
require 'jung/drivers/infobip/campaign.rb'
|
5
|
+
|
6
|
+
attr_reader :list_id, :api, :id
|
7
|
+
attr_writer :list_id, :api
|
8
|
+
|
9
|
+
def self.extended(base)
|
10
|
+
return if base.class != Jung::Campaign
|
11
|
+
base.extend Jung::Drivers::Infobip::Campaign
|
12
|
+
base.api = Api.new base.config
|
13
|
+
end
|
14
|
+
|
15
|
+
def errors
|
16
|
+
api.errors
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Jung::Drivers::Infobip::Api
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'gsm_encoder'
|
5
|
+
require 'jung/drivers/infobip/sms_counter'
|
6
|
+
|
7
|
+
attr_reader :username, :password, :api_url, :error_messages
|
8
|
+
attr_accessor :errors
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
@username = config.options["username"]
|
12
|
+
@password = config.options["password"]
|
13
|
+
@api_url = config.options["api_url"]
|
14
|
+
@errors = []
|
15
|
+
|
16
|
+
@error_messages = {
|
17
|
+
-2 => "Not enough credits",
|
18
|
+
-3 => "Network not covered",
|
19
|
+
-5 => "Invalid username or password",
|
20
|
+
-6 => "Missing destination address",
|
21
|
+
-7 => "Missing SMS text",
|
22
|
+
-8 => "Missing sender name",
|
23
|
+
-9 => "Invalid format of destination address",
|
24
|
+
-10 => "Missing username",
|
25
|
+
-11 => "Missing password",
|
26
|
+
-13 => "Invalid destination address"
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def send_sms(address, message, sender, options = {})
|
31
|
+
count = Jung::Drivers::Infobip::SmsCounter.count message.to_s
|
32
|
+
|
33
|
+
type = count[:messages] > 1 ? 'LongSMS' : nil
|
34
|
+
if count[:encoding] == :utf16
|
35
|
+
data_coding = 8
|
36
|
+
encoding = 'UTF-8'
|
37
|
+
else
|
38
|
+
data_coding = 1
|
39
|
+
encoding = nil
|
40
|
+
message = GSMEncoder.encode message
|
41
|
+
end
|
42
|
+
|
43
|
+
do_get_request :sendsms, {
|
44
|
+
:GSM => address,
|
45
|
+
:SMSText => message,
|
46
|
+
:sender => sender,
|
47
|
+
:Type => type,
|
48
|
+
:DataCoding => data_coding,
|
49
|
+
:encoding => encoding,
|
50
|
+
}.merge(options)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def get_error_message id
|
56
|
+
error_messages[id.to_i] || "Unknow error ID: #{id}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def do_get_request method, params
|
60
|
+
url = api_url + '/' + method.to_s + '/plain?user=' + username + '&password=' + password
|
61
|
+
url = params.reduce(url) do | url, param |
|
62
|
+
url + '&' + CGI.escape(param.first.to_s) + '=' + CGI.escape(param.last.to_s)
|
63
|
+
end
|
64
|
+
result = Net::HTTP.get_response(URI.parse(url)).body
|
65
|
+
|
66
|
+
self.errors << get_error_message(result)
|
67
|
+
|
68
|
+
result.to_i > 1 ? result : false
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Jung::Drivers::Infobip::Campaign
|
2
|
+
|
3
|
+
attr_accessor :username, :password, :api_url, :messages_ids
|
4
|
+
attr_accessor :app_id # Warning: Infobip limitation: app_id should not be more than 30 chars
|
5
|
+
attr_accessor :webhook
|
6
|
+
|
7
|
+
attr_reader :deliver_at
|
8
|
+
def deliver_at=(val)
|
9
|
+
@deliver_in = nil
|
10
|
+
@deliver_at = val
|
11
|
+
end
|
12
|
+
|
13
|
+
def deliver_in
|
14
|
+
return @deliver_in = nil unless deliver_at.respond_to?(:to_time)
|
15
|
+
|
16
|
+
@deliver_in ||= begin
|
17
|
+
|
18
|
+
time = deliver_at.to_time
|
19
|
+
seconds_from_now = (Time.now - time) * -1
|
20
|
+
|
21
|
+
unless seconds_from_now < 0
|
22
|
+
minutes_from_now, seconds_from_now = seconds_from_now.divmod(60)
|
23
|
+
hours_from_now, minutes_from_now = minutes_from_now.divmod(60)
|
24
|
+
days_from_now, hours_from_now = hours_from_now.divmod(24)
|
25
|
+
seconds_from_now = seconds_from_now.to_i
|
26
|
+
|
27
|
+
time_from_now = {
|
28
|
+
:d => days_from_now,
|
29
|
+
:h => hours_from_now,
|
30
|
+
:m => minutes_from_now,
|
31
|
+
:s => seconds_from_now
|
32
|
+
}.reject { |k,v| v == 0 }.map { |(k,v)| "#{v}#{k}" }.join
|
33
|
+
else
|
34
|
+
time_from_now = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
time_from_now
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def deliver(recipients = self.recipients)
|
43
|
+
messages_ids = recipients.map do | recipient |
|
44
|
+
address = recipient.is_a?(Jung::Recipient) ? recipient.address : recipient
|
45
|
+
deliver_recipient address, message
|
46
|
+
end
|
47
|
+
delivery_success? messages_ids
|
48
|
+
end
|
49
|
+
|
50
|
+
def deliver_test(recipients)
|
51
|
+
deliver(recipients)
|
52
|
+
end
|
53
|
+
|
54
|
+
def schedule time
|
55
|
+
self.deliver_at = time
|
56
|
+
deliver
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def delivery_options
|
63
|
+
{ :appid => app_id, :pushurl => webhook, :sendDateTime => deliver_in }.reject{ |k,v| v.nil? }
|
64
|
+
end
|
65
|
+
|
66
|
+
def deliver_recipient address, message
|
67
|
+
api.send_sms address, message, sender.name, delivery_options
|
68
|
+
end
|
69
|
+
|
70
|
+
def delivery_success?(messages_ids)
|
71
|
+
messages_ids.reduce(true) do | acc, value |
|
72
|
+
acc && value != false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module Jung::Drivers::Infobip
|
4
|
+
|
5
|
+
module SmsCounter
|
6
|
+
|
7
|
+
@gsm7bitChars = "@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\"#¤%&'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà"
|
8
|
+
@gsm7bitExChar = "^{}\\[~]|€"
|
9
|
+
|
10
|
+
@gsm7bitRegExp = Regexp.new("^[#{Regexp.escape(@gsm7bitChars)}]*$")
|
11
|
+
@gsm7bitExRegExp = Regexp.new("^[#{Regexp.escape(@gsm7bitChars)}#{Regexp.escape(@gsm7bitExChar)}]*$")
|
12
|
+
@gsm7bitExOnlyRegExp = Regexp.new("^[\\#{Regexp.escape(@gsm7bitExChar)}]*$")
|
13
|
+
|
14
|
+
@messageLength = {
|
15
|
+
:gsm_7bit => 160,
|
16
|
+
:gsm_7bit_ex => 160,
|
17
|
+
:utf16 => 70
|
18
|
+
}
|
19
|
+
|
20
|
+
@multiMessageLength = {
|
21
|
+
:gsm_7bit => 153,
|
22
|
+
:gsm_7bit_ex => 153,
|
23
|
+
:utf16 => 67
|
24
|
+
}
|
25
|
+
|
26
|
+
def self.count(text)
|
27
|
+
encoding = detectEncoding(text)
|
28
|
+
|
29
|
+
length = text.length
|
30
|
+
length += countGsm7bitEx(text) if encoding == :gsm_7bit_ex
|
31
|
+
|
32
|
+
per_message = @messageLength[encoding]
|
33
|
+
per_message = @multiMessageLength[encoding] if length > per_message
|
34
|
+
|
35
|
+
messages = (length.to_f / per_message).ceil
|
36
|
+
remaining = (per_message * messages) - length
|
37
|
+
|
38
|
+
{
|
39
|
+
:encoding => encoding,
|
40
|
+
:length => length,
|
41
|
+
:per_message => per_message,
|
42
|
+
:remaining => remaining,
|
43
|
+
:messages => messages
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.detectEncoding(text)
|
48
|
+
case
|
49
|
+
when text.match(@gsm7bitRegExp) then :gsm_7bit
|
50
|
+
when text.match(@gsm7bitExRegExp) then :gsm_7bit_ex
|
51
|
+
else :utf16
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.countGsm7bitEx(text)
|
56
|
+
chars = text.each_char.select {|char| char.match(@gsm7bitExOnlyRegExp) }
|
57
|
+
chars.size
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Jung::Drivers::Mailchimp
|
2
|
+
|
3
|
+
require 'jung/drivers/mailchimp/api.rb'
|
4
|
+
require 'jung/drivers/mailchimp/list.rb'
|
5
|
+
require 'jung/drivers/mailchimp/campaign.rb'
|
6
|
+
|
7
|
+
attr_reader :gb, :list_id, :api, :id
|
8
|
+
attr_writer :gb, :list_id, :api
|
9
|
+
|
10
|
+
def self.extended(base)
|
11
|
+
base.extend Jung::Drivers::Mailchimp::List
|
12
|
+
base.extend Jung::Drivers::Mailchimp::Campaign if base.class == Jung::Campaign
|
13
|
+
|
14
|
+
base.list_id = base.config.options[:list_id]
|
15
|
+
base.api = Api.new base.config
|
16
|
+
end
|
17
|
+
|
18
|
+
def errors
|
19
|
+
api.errors
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
class Jung::Drivers::Mailchimp::Api
|
2
|
+
|
3
|
+
attr_reader :config, :gb, :list_id
|
4
|
+
attr_accessor :errors
|
5
|
+
|
6
|
+
def initialize(config)
|
7
|
+
@config = config
|
8
|
+
@gb = Gibbon.new config.options["api_key"]
|
9
|
+
@list_id = config.options["list_id"]
|
10
|
+
@errors = []
|
11
|
+
end
|
12
|
+
|
13
|
+
# List Related
|
14
|
+
|
15
|
+
def list_ensure_merge_vars(recipient)
|
16
|
+
recipient.attribute_names.map do |attribute_name|
|
17
|
+
list_merge_var_add(attribute_name)
|
18
|
+
end.reduce &:&
|
19
|
+
end
|
20
|
+
|
21
|
+
def list_merge_var_add(merge_var)
|
22
|
+
tag = merge_var.to_s.upcase
|
23
|
+
name = tag.capitalize
|
24
|
+
|
25
|
+
@merge_vars ||= list_merge_vars
|
26
|
+
|
27
|
+
return true unless !@merge_vars.include? tag
|
28
|
+
|
29
|
+
# Cache is never ugly
|
30
|
+
@merge_vars << tag
|
31
|
+
|
32
|
+
gb.list_merge_var_add({
|
33
|
+
:id => list_id,
|
34
|
+
:tag => tag,
|
35
|
+
:name => name
|
36
|
+
})
|
37
|
+
end
|
38
|
+
|
39
|
+
def list_merge_vars
|
40
|
+
merge_vars_array = gb.list_merge_vars :id => list_id
|
41
|
+
merge_vars_array.map { |e| e["tag"] }
|
42
|
+
end
|
43
|
+
|
44
|
+
def list_subscribe(recipient)
|
45
|
+
merge_vars = recipient.attributes.inject("FNAME" => recipient.name) do |attributes, (k, v)|
|
46
|
+
attributes[k.to_s.upcase] = v
|
47
|
+
attributes
|
48
|
+
end
|
49
|
+
|
50
|
+
add_errors_and_return(gb.list_subscribe({
|
51
|
+
:id => list_id,
|
52
|
+
:email_address => recipient.address,
|
53
|
+
:merge_vars => merge_vars,
|
54
|
+
:double_optin => false,
|
55
|
+
:update_existing => true
|
56
|
+
})) { self == true }
|
57
|
+
end
|
58
|
+
|
59
|
+
def list_unsubscribe(address)
|
60
|
+
gb.list_unsubscribe({
|
61
|
+
:id => list_id,
|
62
|
+
:email_address => address,
|
63
|
+
:delete_member => false,
|
64
|
+
:send_goodbye => false
|
65
|
+
})
|
66
|
+
end
|
67
|
+
|
68
|
+
def list_members
|
69
|
+
current_members = []
|
70
|
+
members_array = gb.list_members :id => list_id, :limit => 15000
|
71
|
+
members_array["data"].each do |member|
|
72
|
+
# TODO: Batch this method (Mailchimp supports up to 50 per call)
|
73
|
+
info = gb.list_member_info :id => list_id, :email_address => member["email"]
|
74
|
+
|
75
|
+
attributes = { :name => info["data"][0]["merges"].delete("FNAME"), :address => info["data"][0]["merges"].delete("EMAIL") }
|
76
|
+
info["data"][0]["merges"].each_pair do |k, v|
|
77
|
+
attributes[k.downcase.to_sym] = v
|
78
|
+
end
|
79
|
+
|
80
|
+
current_members << Jung::Recipient.new(attributes)
|
81
|
+
end
|
82
|
+
current_members
|
83
|
+
end
|
84
|
+
|
85
|
+
def list_static_segments
|
86
|
+
gb.list_static_segments(:id => list_id).reduce({}) do | acc, v |
|
87
|
+
acc[v["name"]] = v["id"]
|
88
|
+
acc
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def list_wipe_static_segments
|
93
|
+
# TODO: Call this only to unused segments. Internal use only for now.
|
94
|
+
list_static_segments.each do | campaign_id, segment_id |
|
95
|
+
list_static_segment_delete segment_id
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def find_or_add_static_segment name
|
100
|
+
list_static_segment_find(name) ||
|
101
|
+
list_static_segment_add(name)
|
102
|
+
end
|
103
|
+
|
104
|
+
def list_static_segment_add name
|
105
|
+
gb.list_static_segment_add :id => list_id, :name => name
|
106
|
+
end
|
107
|
+
|
108
|
+
def list_static_segment_find name
|
109
|
+
static_segments = list_static_segments
|
110
|
+
static_segments[name]
|
111
|
+
end
|
112
|
+
|
113
|
+
def list_static_segment_reset static_segment_id
|
114
|
+
gb.list_static_segment_members_add :id => list_id, :seg_id => static_segment_id
|
115
|
+
end
|
116
|
+
|
117
|
+
def list_static_segment_delete static_segment_id
|
118
|
+
gb.list_static_segment_del :id => list_id, :seg_id => static_segment_id
|
119
|
+
end
|
120
|
+
|
121
|
+
def list_static_segment_members_add static_segment_id, emails
|
122
|
+
add_errors_and_return(gb.list_static_segment_members_add({
|
123
|
+
:id => list_id,
|
124
|
+
:seg_id => static_segment_id,
|
125
|
+
:batch => emails
|
126
|
+
})) { self["success"] == emails.count }
|
127
|
+
end
|
128
|
+
|
129
|
+
# Campaign Related
|
130
|
+
|
131
|
+
def all_campaigns
|
132
|
+
gb.campaigns({ :filters => { :list_id => list_id } })
|
133
|
+
end
|
134
|
+
|
135
|
+
def campaign_create campaign
|
136
|
+
add_errors_and_return(gb.campaign_create({
|
137
|
+
:type => :regular,
|
138
|
+
:options => {
|
139
|
+
:list_id => list_id,
|
140
|
+
:title => campaign.name,
|
141
|
+
:subject => campaign.subject,
|
142
|
+
:from_name => campaign.sender.name,
|
143
|
+
:from_email => campaign.sender.address,
|
144
|
+
:to_name => '*|FNAME|*',
|
145
|
+
:generate_text => true,
|
146
|
+
:fb_comments => false,
|
147
|
+
:inline_css => config.options["inline_css"] || true
|
148
|
+
},
|
149
|
+
:content => {
|
150
|
+
:html => campaign.message
|
151
|
+
}
|
152
|
+
})) { gsub(/\"/, '') }
|
153
|
+
end
|
154
|
+
|
155
|
+
def campaign_update campaign
|
156
|
+
# campaign.pry
|
157
|
+
end
|
158
|
+
|
159
|
+
def campaign id
|
160
|
+
add_errors_and_return(gb.campaigns({
|
161
|
+
:filters => {
|
162
|
+
:campaign_id => id
|
163
|
+
}
|
164
|
+
})) { self["data"].first }
|
165
|
+
end
|
166
|
+
|
167
|
+
def campaign_content id
|
168
|
+
campaign_content = gb.campaign_content :cid => id
|
169
|
+
campaign_content["html"]
|
170
|
+
end
|
171
|
+
|
172
|
+
def campaign_send_now id
|
173
|
+
gb.campaign_send_now :cid => id
|
174
|
+
end
|
175
|
+
|
176
|
+
def campaign_send_test id, recipients
|
177
|
+
gb.campaign_send_test :cid => id, :test_emails => recipients
|
178
|
+
end
|
179
|
+
|
180
|
+
def campaign_delete id
|
181
|
+
gb.campaign_delete :cid => id
|
182
|
+
end
|
183
|
+
|
184
|
+
def campaign_update_static_segment id, static_segment_id
|
185
|
+
gb.campaign_update :cid => id, :name => "segment_opts", :value => {
|
186
|
+
:match => "all",
|
187
|
+
:conditions => [{ :field => "static_segment", :op => "eq", :value => static_segment_id }]
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
def campaign_schedule id, time
|
192
|
+
gb.campaign_schedule :cid => id, :schedule_time => time.utc.strftime('%Y-%m-%d %H:%M:%S')
|
193
|
+
end
|
194
|
+
|
195
|
+
def campaign_unschedule id
|
196
|
+
gb.campaign_unschedule :cid => id
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
def add_errors_and_return(result, &proc)
|
202
|
+
if result.is_a?(Hash) && result["error"]
|
203
|
+
self.errors ||= [] << result["code"].to_s + ' - ' + result["error"]
|
204
|
+
false
|
205
|
+
else
|
206
|
+
result.instance_eval(&proc)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Jung::Drivers::Mailchimp::Campaign
|
2
|
+
|
3
|
+
def find(id)
|
4
|
+
@id = id
|
5
|
+
campaign = api.campaign id
|
6
|
+
|
7
|
+
return false if !campaign
|
8
|
+
|
9
|
+
campaign_content = api.campaign_content id
|
10
|
+
|
11
|
+
@name = campaign["title"]
|
12
|
+
@subject = campaign["subject"]
|
13
|
+
@message = campaign_content
|
14
|
+
@sender = Jung::Sender.new(campaign["from_name"], campaign["from_email"])
|
15
|
+
end
|
16
|
+
|
17
|
+
def save
|
18
|
+
# if sync_merge_vars &&
|
19
|
+
if sync_members &&
|
20
|
+
sync_campaign &&
|
21
|
+
sync_static_segments
|
22
|
+
@id
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def deliver
|
27
|
+
api.campaign_send_now(id) if save
|
28
|
+
end
|
29
|
+
|
30
|
+
def deliver_test(recipients)
|
31
|
+
api.campaign_send_test(id, recipients) if save
|
32
|
+
end
|
33
|
+
|
34
|
+
def schedule time
|
35
|
+
api.campaign_schedule(id, time) if save
|
36
|
+
end
|
37
|
+
|
38
|
+
def unschedule
|
39
|
+
api.campaign_unschedule(id) if id
|
40
|
+
end
|
41
|
+
|
42
|
+
def delete
|
43
|
+
delete_static_segment &&
|
44
|
+
api.campaign_delete(id) &&
|
45
|
+
reset
|
46
|
+
end
|
47
|
+
|
48
|
+
def all
|
49
|
+
api.all_campaigns
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def reset
|
55
|
+
@id = nil
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
def sync_campaign
|
60
|
+
# TODO: Refactor with ||
|
61
|
+
if id
|
62
|
+
api.campaign_update self
|
63
|
+
puts "Not Yet Supported"
|
64
|
+
else
|
65
|
+
@id = api.campaign_create self
|
66
|
+
end
|
67
|
+
self.id
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete_static_segment
|
71
|
+
static_segment_id = api.list_static_segment_find id
|
72
|
+
api.list_static_segment_delete static_segment_id
|
73
|
+
end
|
74
|
+
|
75
|
+
def sync_static_segments
|
76
|
+
# Dont list_wipe_static_segments yet!
|
77
|
+
# api.list_wipe_static_segments
|
78
|
+
# The segment name is the campaign internal id. Creates one static segment per campaign
|
79
|
+
static_segment_id = api.find_or_add_static_segment id
|
80
|
+
api.list_static_segment_reset static_segment_id
|
81
|
+
emails = recipients.map { |recipient| recipient.address }
|
82
|
+
api.list_static_segment_members_add static_segment_id, emails
|
83
|
+
api.campaign_update_static_segment id, static_segment_id
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Jung::Drivers::Mailchimp::List
|
2
|
+
|
3
|
+
def save
|
4
|
+
sync_merge_vars &&
|
5
|
+
sync_members &&
|
6
|
+
delete_non_members
|
7
|
+
end
|
8
|
+
|
9
|
+
def all
|
10
|
+
api.list_members
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def sync_merge_vars
|
16
|
+
return true if recipients.count == 0
|
17
|
+
(recipients.map do |recipient|
|
18
|
+
api.list_ensure_merge_vars(recipient)
|
19
|
+
end).reduce &:&
|
20
|
+
end
|
21
|
+
|
22
|
+
def sync_members
|
23
|
+
return true if recipients.count == 0
|
24
|
+
(recipients.map { |recipient| api.list_subscribe(recipient) }).reduce &:&
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete_non_members
|
28
|
+
# Unsubscribe non-existing members
|
29
|
+
# Call this method *ONLY* with a full list of all members
|
30
|
+
current_members = api.list_members
|
31
|
+
current_members.each do |member|
|
32
|
+
res = find_recipient_by_address member.address
|
33
|
+
if !find_recipient_by_address member.address
|
34
|
+
api.list_unsubscribe member.address
|
35
|
+
end
|
36
|
+
end
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/lib/jung/list.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Jung
|
2
|
+
class List
|
3
|
+
|
4
|
+
attr_reader :config
|
5
|
+
attr_accessor :recipients
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
@config = options[:config]
|
9
|
+
@recipients = []
|
10
|
+
|
11
|
+
self.load_driver
|
12
|
+
end
|
13
|
+
|
14
|
+
def load_driver
|
15
|
+
self.extend config.driver_const
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_recipient(attributes)
|
19
|
+
self.recipients << Jung::Recipient.new(attributes)
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_recipient_by_address(address)
|
23
|
+
recipients.each do |recipient|
|
24
|
+
return recipient if recipient.address == address
|
25
|
+
end
|
26
|
+
return nil
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Jung
|
2
|
+
class Recipient
|
3
|
+
|
4
|
+
attr_accessor :name, :address
|
5
|
+
attr_reader :attribute_names
|
6
|
+
attr_reader :attributes
|
7
|
+
|
8
|
+
def initialize(attributes)
|
9
|
+
@attribute_names = []
|
10
|
+
@attributes = {}
|
11
|
+
|
12
|
+
@name = attributes.delete(:name)
|
13
|
+
@address = attributes.delete(:address)
|
14
|
+
|
15
|
+
@attribute_names = attributes.keys.uniq.map(&:to_sym)
|
16
|
+
|
17
|
+
attributes.each_pair do |k, v|
|
18
|
+
@attributes[k.to_sym] = v
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_attribute(attr_name)
|
23
|
+
@attributes[attr_name]
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_attribute(attr_name, value)
|
27
|
+
@attributes[attr_name] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def attribute_method?(method_name)
|
31
|
+
match = method_name.to_s.match(/^(?<attr_name>.*?)(?<setter>=?)$/)
|
32
|
+
accessor = match[:setter] == '=' ? :set : :get
|
33
|
+
attr_name = match[:attr_name].to_sym
|
34
|
+
attribute_names.include?(attr_name) ? [accessor, attr_name] : false
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(method_name, *args, &block)
|
38
|
+
accessor, attr_name = attribute_method?(method_name)
|
39
|
+
if accessor
|
40
|
+
send("#{accessor}_attribute", attr_name, *args, &block)
|
41
|
+
else
|
42
|
+
super method_name, *args, &block
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def respond_to?(method_name, include_private = false)
|
47
|
+
if attribute_method?(method_name)
|
48
|
+
true
|
49
|
+
else
|
50
|
+
super method_name, include_private
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/lib/jung/sender.rb
ADDED
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'jung'
|
16
|
+
|
17
|
+
class Test::Unit::TestCase
|
18
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestJung < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def config
|
6
|
+
config_file_name = File.join(Pathname.new(__FILE__).dirname, "config/jung.yml")
|
7
|
+
@config ||= Jung::Config.load config_file_name, :test_infobip
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_campaign_deliver
|
11
|
+
# First lets define a campaign
|
12
|
+
rand = rand(123 * 456)
|
13
|
+
campaign = Jung::Campaign.new({
|
14
|
+
:config => config,
|
15
|
+
:name => 'JUNG TEST - Lorem ipsum dolor. #' + rand.to_s,
|
16
|
+
:sender => Jung::Sender.new('Lorem ipsum')
|
17
|
+
})
|
18
|
+
|
19
|
+
# Add the recipients
|
20
|
+
campaign.create_recipient :name => 'Lorem', :address => config.options["fixtures"]["test_phone1"]
|
21
|
+
campaign.create_recipient :name => 'Lipsum', :address => config.options["fixtures"]["test_phone2"]
|
22
|
+
|
23
|
+
campaign.message = campaign.name
|
24
|
+
|
25
|
+
assert campaign.deliver
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestJung < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def config
|
6
|
+
config_file_name = File.join(Pathname.new(__FILE__).dirname, "config/jung.yml")
|
7
|
+
@config ||= Jung::Config.load config_file_name, :test_mailchimp
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_list_save
|
11
|
+
list = Jung::List.new :config => config
|
12
|
+
|
13
|
+
list.create_recipient :name => 'Lorem', :address => 'lorem@mobvox.com.br', :sex => 'm', :function => "Developer", :phone => '33554466'
|
14
|
+
list.create_recipient :name => 'Ipsum', :address => 'ipsum@mobvox.com.br', :sex => 'm', :function => "Developer"
|
15
|
+
list.create_recipient :name => 'Dolor', :address => 'dolor@mobvox.com.br', :sex => 'm', :function => "Developer"
|
16
|
+
list.create_recipient :name => 'Sit', :address => 'sit@mobvox.com.br', :sex => 'm', :function => "Developer"
|
17
|
+
list.create_recipient :name => 'Amet', :address => 'amet@mobvox.com.br', :sex => 'm', :function => "Developer"
|
18
|
+
list.create_recipient :name => 'Consectetur', :address => 'consectetur@mobvox.com.br', :sex => 'f', :function => "Developer"
|
19
|
+
list.create_recipient :name => 'Adipiscing', :address => 'adipiscing@mobvox.com.br', :sex => 'm', :function => "Psychologist"
|
20
|
+
|
21
|
+
assert list.save
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_campaign_save_find_delete
|
25
|
+
# First lets save a campaign
|
26
|
+
rand = rand(123 * 456)
|
27
|
+
campaign = Jung::Campaign.new({
|
28
|
+
:config => config,
|
29
|
+
:name => 'JUNG TEST - Lorem ipsum dolor. #' + rand.to_s,
|
30
|
+
:subject => 'Fusce tempus, nibh eleifend feugiat lobortis.',
|
31
|
+
:sender => Jung::Sender.new('Lorem ipsum', 'contato@mobvox.com.br')
|
32
|
+
})
|
33
|
+
campaign.create_recipient :name => 'Lorem', :address => 'lorem@mobvox.com.br', :sex => 'm', :function => "Developer", :phone => '33554466'
|
34
|
+
campaign.create_recipient :name => 'Ipsum', :address => 'ipsum@mobvox.com.br', :sex => 'm', :function => "Developer"
|
35
|
+
id = campaign.save
|
36
|
+
assert id != nil
|
37
|
+
|
38
|
+
# Now lets "find" the campaign
|
39
|
+
campaign = Jung::Campaign.new :config => config
|
40
|
+
if !campaign.find id
|
41
|
+
puts campaign.errors.inspect
|
42
|
+
end
|
43
|
+
assert campaign.id != nil
|
44
|
+
|
45
|
+
# And delete it!
|
46
|
+
assert campaign.delete
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_campaign_schedule_unschedule_delete
|
50
|
+
# First lets define a campaign
|
51
|
+
rand = rand(123 * 456)
|
52
|
+
campaign = Jung::Campaign.new({
|
53
|
+
:config => config,
|
54
|
+
:name => 'JUNG TEST - Lorem ipsum dolor. #' + rand.to_s,
|
55
|
+
:subject => 'Fusce tempus, nibh eleifend feugiat lobortis.',
|
56
|
+
:sender => Jung::Sender.new('Lorem ipsum', 'contato@mobvox.com.br')
|
57
|
+
})
|
58
|
+
|
59
|
+
# Add one recipient
|
60
|
+
campaign.create_recipient :name => 'Lorem', :address => 'lorem@mobvox.com.br', :sex => 'm', :function => "Developer", :phone => '33554466'
|
61
|
+
|
62
|
+
campaign.subject = campaign.name
|
63
|
+
campaign.message = campaign.name
|
64
|
+
|
65
|
+
assert campaign.schedule Time.now + (60 * 60 * 24)
|
66
|
+
assert campaign.unschedule
|
67
|
+
# assert campaign.deliver
|
68
|
+
assert campaign.delete
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jung
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- arielpts
|
9
|
+
- danxexe
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-10-17 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: gibbon
|
17
|
+
requirement: &2153125540 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *2153125540
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: activesupport
|
28
|
+
requirement: &2153125100 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *2153125100
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: i18n
|
39
|
+
requirement: &2153124680 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *2153124680
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rdoc
|
50
|
+
requirement: &2153124260 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :runtime
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *2153124260
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: gsm_encoder
|
61
|
+
requirement: &2153123840 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
type: :runtime
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: *2153123840
|
70
|
+
description: Have you ever wondered how it would be if there were an easy way to send
|
71
|
+
your message to billions of people? The Jung’s collective unconscious will help
|
72
|
+
in this arduous task!
|
73
|
+
email:
|
74
|
+
- arielpts@me.com
|
75
|
+
executables: []
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- .document
|
80
|
+
- .gitignore
|
81
|
+
- Gemfile
|
82
|
+
- LICENSE.txt
|
83
|
+
- README.rdoc
|
84
|
+
- Rakefile
|
85
|
+
- VERSION
|
86
|
+
- jung.gemspec
|
87
|
+
- lib/jung.rb
|
88
|
+
- lib/jung/campaign.rb
|
89
|
+
- lib/jung/config.rb
|
90
|
+
- lib/jung/drivers.rb
|
91
|
+
- lib/jung/drivers/infobip.rb
|
92
|
+
- lib/jung/drivers/infobip/api.rb
|
93
|
+
- lib/jung/drivers/infobip/campaign.rb
|
94
|
+
- lib/jung/drivers/infobip/sms_counter.rb
|
95
|
+
- lib/jung/drivers/mailchimp.rb
|
96
|
+
- lib/jung/drivers/mailchimp/api.rb
|
97
|
+
- lib/jung/drivers/mailchimp/campaign.rb
|
98
|
+
- lib/jung/drivers/mailchimp/list.rb
|
99
|
+
- lib/jung/list.rb
|
100
|
+
- lib/jung/recipient.rb
|
101
|
+
- lib/jung/sender.rb
|
102
|
+
- test/helper.rb
|
103
|
+
- test/test_jung_infobip.rb
|
104
|
+
- test/test_jung_mailchimp.rb
|
105
|
+
homepage: http://www.mobvox.com.br
|
106
|
+
licenses: []
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ! '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ! '>='
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
requirements: []
|
124
|
+
rubyforge_project:
|
125
|
+
rubygems_version: 1.8.10
|
126
|
+
signing_key:
|
127
|
+
specification_version: 3
|
128
|
+
summary: Eletronic message deliver proxy
|
129
|
+
test_files:
|
130
|
+
- test/helper.rb
|
131
|
+
- test/test_jung_infobip.rb
|
132
|
+
- test/test_jung_mailchimp.rb
|
133
|
+
has_rdoc:
|