jung 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ .bundle
2
+ db/*.sqlite3
3
+ log/*.log
4
+ tmp/
5
+ .sass-cache/
6
+ .DS_Store
7
+ ._*
8
+ Gemfile.lock
9
+ .AppleDouble
10
+ test/config/*.yml
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,10 @@
1
+ require 'active_support/inflector'
2
+ require 'gibbon'
3
+ require 'pry'
4
+
5
+ require 'jung/sender.rb'
6
+ require 'jung/list.rb'
7
+ require 'jung/campaign.rb'
8
+ require 'jung/drivers.rb'
9
+ require 'jung/config.rb'
10
+ require 'jung/recipient.rb'
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ module Jung::Drivers
2
+
3
+ def self.load(name)
4
+ require File.join('jung', 'drivers', name)
5
+ end
6
+
7
+ end
@@ -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
@@ -0,0 +1,12 @@
1
+ module Jung
2
+ class Sender
3
+
4
+ attr_accessor :name, :address
5
+
6
+ def initialize(name, address = nil)
7
+ @name = name
8
+ @address = address
9
+ end
10
+
11
+ end
12
+ end
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: