distribution_wrappers 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +102 -0
  3. data/Gemfile.lock +295 -0
  4. data/README.md +22 -0
  5. data/Rakefile +53 -0
  6. data/VERSION +1 -0
  7. data/distribution_wrappers.gemspec +173 -0
  8. data/lib/config/key_chain.yml +70 -0
  9. data/lib/config/key_chain_dev.yml +71 -0
  10. data/lib/distribution_wrappers.rb +64 -0
  11. data/lib/distribution_wrappers/_base.rb +68 -0
  12. data/lib/distribution_wrappers/backstitch/backstitch.rb +267 -0
  13. data/lib/distribution_wrappers/email/_email.rb +48 -0
  14. data/lib/distribution_wrappers/email/template.html.haml +29 -0
  15. data/lib/distribution_wrappers/google/google.rb +129 -0
  16. data/lib/distribution_wrappers/hipchat/hipchat.rb +80 -0
  17. data/lib/distribution_wrappers/office365/office365.rb +51 -0
  18. data/lib/distribution_wrappers/sendgrid/sendgrid.rb +35 -0
  19. data/lib/distribution_wrappers/slack/slack.rb +87 -0
  20. data/lib/helpers/keys.rb +10 -0
  21. data/lib/helpers/text_helper.rb +9 -0
  22. data/lib/models/device_session_distro.rb +5 -0
  23. data/lib/models/distribution_channel_distro.rb +4 -0
  24. data/lib/models/feed_distro.rb +11 -0
  25. data/lib/models/organization_admin_distro.rb +5 -0
  26. data/lib/models/organization_distro.rb +10 -0
  27. data/lib/models/studio_post_version_contact_distro.rb +5 -0
  28. data/lib/models/studio_post_version_distro.rb +5 -0
  29. data/lib/models/team_distro.rb +13 -0
  30. data/lib/models/team_feed_distro.rb +5 -0
  31. data/lib/models/team_member_distro.rb +6 -0
  32. data/lib/models/topic_distro.rb +11 -0
  33. data/lib/models/topic_feed_distro.rb +5 -0
  34. data/lib/models/topic_subscription_distro.rb +9 -0
  35. data/lib/models/user_distro.rb +34 -0
  36. data/test/helper.rb +34 -0
  37. data/test/test_distribution_wrappers.rb +7 -0
  38. metadata +556 -0
@@ -0,0 +1,267 @@
1
+ module DistributionWrappers
2
+ class Backstitch < DistributionWrappers::Base
3
+
4
+ def send_message(recipient)
5
+ super
6
+ @response = Semantic::OrganizationResults::Article.bulk_store([@message], true, true)
7
+ results = []
8
+
9
+ @version_contact.channel_entity_identifier = @message[:reference_id]
10
+ @version_contact.save
11
+
12
+ # send push notification if priority alerts is turned on for the selected feed
13
+ feed = FeedDistro.find(recipient['identifier'].to_i)
14
+ if feed.params["priority_alerts"] && @message
15
+ pushwoosh_id = feed.owner.pushwoosh_id if feed.owner_type == "Organization" && feed.owner.pushwoosh_id
16
+ organization_id = feed.owner.id if feed.owner && feed.owner_type == "Organization"
17
+ send_notifications(@message, feed.id, organization_id, pushwoosh_id)
18
+ end
19
+ end
20
+
21
+ def get_contacts
22
+ channel = DistributionChannelDistro.find @params[:channel_id]
23
+ user = UserDistro.find(channel.owner_id)
24
+
25
+ feeds = user.managed_feeds
26
+
27
+ csv_string = ""
28
+
29
+ feeds.each_with_index do |feed, index|
30
+ csv_string += CSV.generate_line [feed.name, feed.feed_id, '{"url": null}', 'feed', @params[:channel_id]]
31
+
32
+ if index % 100 == 0
33
+ @params[:temp_file].write(csv_string)
34
+ csv_string = ""
35
+ end
36
+ end
37
+
38
+ @params[:temp_file].write(csv_string)
39
+
40
+ return true
41
+ end
42
+
43
+ private
44
+
45
+ def prepare(identifier=nil)
46
+ published_at = Time.now.utc.to_datetime
47
+ doc = Nokogiri::HTML(@params[:post].draft_content)
48
+
49
+ doc = append_parameters(doc, true)
50
+ doc = update_feedback_trackers(doc) if doc.css('td[data-cell-identifier="feedback-form-container"]').length > 0
51
+
52
+ doc = redirect_links(doc, true)
53
+
54
+ if @params[:reference_id]
55
+ result = Semantic::OrganizationResults::Article.find_by_reference_id(@params[:reference_id])
56
+ else
57
+ result = Semantic::OrganizationResults::Article.new("feed-#{identifier}-#{@params[:user].id}-#{published_at.to_i}")
58
+ end
59
+
60
+ build_result(doc, result, identifier)
61
+ end
62
+
63
+ def update_feedback_trackers(doc)
64
+ doc.css('td[data-cell-identifier="feedback-form-container"]').each do |feedback|
65
+ identifier = feedback.css('a').attr('data-url-identifier')
66
+ feedback.inner_html = "<textarea id='#{identifier}' style='width:100%;height:100%;outline:none;border:none;resize:none;'></textarea>"
67
+ end
68
+
69
+ doc.css('td[data-cell-identifier="feedback-submit-container"]').each do |feedback|
70
+ base_url = feedback.css('a').attr('href')
71
+ feedback.css('a').attr('href').value = base_url.value.gsub('feedback_form', 'record_feedback')
72
+ end
73
+
74
+ doc
75
+ end
76
+
77
+ def build_result(doc, result, identifier)
78
+ begin
79
+ published_at = Time.now.utc.to_datetime
80
+
81
+ created_by = UserDistro.find @params[:post].created_by_id
82
+ result[:published_at] = published_at
83
+ result[:title] = @params[:post].title
84
+ result[:plain_text_title] = TextHelper.clean_text(result[:title])
85
+ result[:author][:name] = @params[:user].display_name
86
+ result[:tracking] = {
87
+ :type => "text",
88
+ :feed_id => identifier.to_i
89
+ }
90
+
91
+ result[:origin][:id] = created_by.id
92
+ result[:origin][:name] = created_by.display_name
93
+ result[:origin][:icon][:url] = created_by.avatar_url
94
+
95
+ if @params[:post].draft_content.match(/<html>.*<\/html>/).nil?
96
+ result[:full_text] = "<html>#{@params[:post].draft_content}</html>"
97
+ else
98
+ result[:full_text] = @params[:post].draft_content
99
+ end
100
+
101
+ result[:images][:full_size][:url] = @params[:post].main_image if @params[:post].main_image
102
+
103
+ video_iframes = doc.css('iframe').select{|iframe| URI.unescape(iframe.attr('src')).match(/(?<=youtube\.com\/embed\/)(.+?)(?=\?|$)/)}
104
+
105
+ video_iframes.each do |iframe|
106
+ iframe['style'] = "box-sizing: content-box; max-width: 100%; border: 0px;"
107
+ iframe['height'] = '360'
108
+ iframe['width'] = '640'
109
+ end
110
+
111
+ if result[:images][:full_size][:url].nil?
112
+ if img = doc.xpath('//iframe').first
113
+ if img.attr('src').match(/youtube/) ||
114
+ img.attr('src').match(/dailymotion/) ||
115
+ img.attr('src').match(/wistia/)
116
+ VideoInfo.provider_api_keys = {youtube: Keys.google.key, vimeo: Keys.vimeo.client_id}
117
+ video = VideoInfo.new(img.attr('src'))
118
+ article[:images][:full_size][:url] = video.thumbnail_large
119
+ end
120
+ end
121
+ end
122
+
123
+ base_url = ""
124
+ if ENV['ENVIRONMENT'] == 'production'
125
+ base_url = "http://studio.backstit.ch"
126
+ else
127
+ base_url = "http://localhost:3000"
128
+ end
129
+
130
+ new_node = doc.create_element 'div'
131
+ new_node.inner_html = "<img src='#{base_url}/posts/#{@params[:post].id}/engagement/tracking.gif?contact_id=#{@params[:version_contact_id]}&version_id=#{@params[:version_id]}&organization_id=#{@params[:organization].id}&email=<<email_placeholder>>'/>"
132
+ doc.children[1].add_child(new_node)
133
+
134
+ result[:full_text] = doc.to_html
135
+
136
+ result[:plain_text] = TextHelper.clean_text(result[:full_text])
137
+
138
+ summary = Pismo::Document.new(result[:plain_text])
139
+ result[:description] = summary.lede
140
+ result[:plain_text_description] = TextHelper.clean_text(result[:description])
141
+
142
+ base_url = ""
143
+ if ENV['ENVIRONMENT'] == 'production'
144
+ base_url = "http://backstit.ch"
145
+ else
146
+ base_url = "http://localhost:3000"
147
+ end
148
+ result[:origin][:url] = "#{base_url}/feeds/#{result[:tracking][:feed_id]}/results/#{result[:reference_id]}"
149
+
150
+ return result
151
+ rescue
152
+ end
153
+ end
154
+
155
+ def send_notifications(result, feed_id, organization_id=nil, pushwoosh_id=nil)
156
+ Pushwoosh.configure do |config|
157
+ config.auth = 'VvFYDCs1zaz5PIy9WgC7KDfSmAD6Sqlykrxw1mxsXnfSgRG07k84wuCD4hBRAt97EyvDJQKl2ewy01h8olzV'
158
+ if pushwoosh_id
159
+ config.application = pushwoosh_id
160
+ else
161
+ config.application = 'FF35B-68BC0'
162
+ end
163
+ end
164
+
165
+ topics = TopicDistro.joins(:topic_feeds).where('topic_feeds.feed_id = ?', feed_id)
166
+
167
+ body = []
168
+ topics.each do |topic|
169
+ internal_source_count = topic.feeds.where("feeds.owner_id is not null").count
170
+ if topic.alias? && ((topic.promoted_alias? && internal_source_count > 0) || internal_source_count == 0)
171
+ alias_name = "#{topic.alias}"
172
+ alias_name = "#{alias_name},#{topic.promoted_alias}" if internal_source_count > 0
173
+ elsif topic.serialized_alias? && ((topic.serialized_promoted_alias? && internal_source_count > 0) || internal_source_count == 0)
174
+ indices = topic.serialized_alias["indices"]
175
+ indices.concat(topic.serialized_promoted_alias["indices"]) unless topic.serialized_promoted_alias.nil?
176
+
177
+ should = []
178
+ should << {topic.serialized_alias["action"] => topic.serialized_alias["filters"]}
179
+ should << {topic.serialized_promoted_alias["action"] => topic.serialized_promoted_alias["filters"]} unless topic.serialized_promoted_alias.nil?
180
+
181
+ alias_name = indices.uniq.join(",")
182
+ filters = {"bool" => {"should" => should}}
183
+ else
184
+ next
185
+ end
186
+
187
+ query = {
188
+ :query => {
189
+ :term => {
190
+ :reference_id => result[:reference_id]
191
+ }
192
+ }
193
+ }
194
+
195
+ query[:filter] = filters if filters
196
+
197
+ body << {:search_type => 'count', :search => query, :index => alias_name, :ignore_indices => "missing", :topic_id => topic.id}
198
+ end
199
+
200
+ options = {
201
+ :body => body
202
+ }
203
+
204
+ search = ELASTICSEARCH.msearch options
205
+
206
+ topic_ids = []
207
+
208
+ body.each_with_index do |topic, index|
209
+ if search["responses"][index]["hits"]["total"] > 0
210
+ topic_ids << topic[:topic_id]
211
+ end
212
+ end
213
+
214
+ # If a user hasn't signed into the app, send them a priority email instead of a push notification
215
+ users = UserDistro.joins(:topics).joins("left join device_sessions on device_sessions.user_id = users.id")
216
+ .where("topics.id in (?) AND device_sessions.id is null", topic_ids)
217
+ .uniq.pluck(:id).compact
218
+
219
+ # Pull device sessions for every user that's signed into the app so we can send push notifications
220
+ device_sessions = DeviceSessionDistro.joins(:user => :topics).where("topics.id in (?)", topic_ids)
221
+ .uniq.pluck(:device_token).compact.delete_if{|token| token == "" }
222
+
223
+
224
+ users.each do |user_id|
225
+ # Mailer::PriorityAlertWorker.perform_async(user_id, organization_id, result)
226
+ end
227
+
228
+ message = ""
229
+
230
+ case result[:type]
231
+ when 'article'
232
+ plain_text = ""
233
+ plain_text = " - #{result[:plain_text][0..140]}" if result[:plain_text]
234
+ message = "#{result[:title]}#{plain_text}"
235
+ when 'status', 'photo'
236
+ message = "#{result[:plain_text_description][0..140]}"
237
+ when 'video'
238
+ message = "#{result[:title]} - #{result[:description][0..140]}"
239
+ when 'email'
240
+ message = "#{result[:subject]} - #{result[:plain_text][0..140]}"
241
+ when "product", "service", "hotel"
242
+ message = "#{result[:title][:long][0..140]}"
243
+ end
244
+
245
+ options = {
246
+ "send_date" => "now",
247
+ "ignore_user_timezone" => true,
248
+ "platforms" => [1,3],
249
+ "data" => {
250
+ "ref_id" => result[:reference_id],
251
+ "feed_id" => feed_id.to_s
252
+ }
253
+ }
254
+
255
+ #ios options
256
+ options["ios_badges"] = "+1"
257
+ options["apns_trim_content"] = 1
258
+
259
+ # android options
260
+ options["android_badges"] = "+1"
261
+
262
+ device_sessions.each_slice(1000) do |tokens|
263
+ Pushwoosh.notify_devices(message, tokens, options)
264
+ end
265
+ end
266
+ end
267
+ end
@@ -0,0 +1,48 @@
1
+ module DistributionWrappers
2
+ class Email < DistributionWrappers::Base
3
+
4
+ def prepare(email)
5
+ @params[:email] = email
6
+ template = Tilt::HamlTemplate.new(File.join(File.dirname(__FILE__), 'template.html.haml'))
7
+
8
+ doc = Nokogiri::HTML(@params[:post].draft_content)
9
+
10
+ doc = replace_video(doc)
11
+
12
+ doc = append_parameters(doc)
13
+
14
+ doc = redirect_links(doc)
15
+
16
+ @params[:draft_content] = html
17
+
18
+ body_html = template.render(@params)
19
+
20
+ body_html
21
+ end
22
+
23
+ private
24
+
25
+ def replace_video(doc)
26
+ VideoInfo.provider_api_keys = {youtube: '294129192803.apps.googleusercontent.com', vimeo: 'cb5e2b895a44ce8a74336fc1dde69f2e9e2ca172'}
27
+ video_iframes = doc.css('span iframe').select{|iframe| URI.unescape(iframe.attr('src')).match(/(?<=youtube\.com\/embed\/)(.+?)(?=\?|$)/)}
28
+
29
+ video_iframes.each do |iframe|
30
+ id = URI.unescape(iframe.attr('src')).scan(/(?<=youtube\.com\/embed\/)(.+?)(?=\?|$)/).flatten[0]
31
+ new_node = doc.create_element "div"
32
+ video_info = VideoInfo.new("http://www.youtube.com/watch?v=#{id}")
33
+ new_node.inner_html = """<table width='100%' border='0' cellspacing='0' cellpadding='0'>
34
+ <tr>
35
+ <td align='center'>
36
+ <a href='http://www.youtube.com/watch?v=#{id}'>
37
+ <img src='#{video_info.thumbnail_large}' style='height:360px;width:480px;display:block;'>
38
+ <img src='http://images-backstitch.s3.amazonaws.com/emails/video_info.png' style='width:480px;display:block;'>
39
+ </a>
40
+ </td>
41
+ </tr>
42
+ </table>"""
43
+ iframe.parent.replace new_node
44
+ end
45
+ doc
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,29 @@
1
+ !!!
2
+
3
+ - if ENV['ENVIRONMENT'] == "development"
4
+ - base_url = "http://localhost:3000"
5
+ - else
6
+ - base_url = "http://studio.backstit.ch"
7
+
8
+ %html
9
+ %head
10
+ %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
11
+ %body{:style => "margin: 0px;"}
12
+ %table{:width => '100%', :border => 0, :cellspacing => 0, :cellpadding => '0px', :style => "background: #f1f1f1; border: 1px solid #e4e4e4; color: #3e3e3e;"}
13
+ %tr
14
+ %td{:align => 'center'}
15
+ -# container
16
+ %table{:width => '600', :border => 0, :cellspacing => 0, :cellpadding => 0, :style => "width: 600px;background: #ffffff;padding:8px"}
17
+ %tr
18
+ %td
19
+ != self[:draft_content]
20
+
21
+ %tr{:style => "font-family: Helvetica Neue,Helvetica,Arial,sans-serif; font-size: 12px; color: #656565; text-align: center;"}
22
+ %td{:style => "padding: 10px 0px;"}
23
+ %p
24
+ %b{:style => "color: #{self[:organization].highlight_color}"}
25
+ = self[:organization].name
26
+ sent you this message using backstitch Studio.
27
+ %p{:style => "color: #656565"}
28
+ ©2016 backstitch Inc. 1528 Woodward Ave. 3rd Floor, Detroit, MI 48226
29
+ %img{:src => "#{base_url}/posts/#{self[:post].id}/engagement/tracking.gif?contact_id=#{self[:version_contact_id]}&version_id=#{self[:version_id]}&organization_id=#{self[:organization].id}"}
@@ -0,0 +1,129 @@
1
+ # require 'google/apis/gmail_v1'
2
+ # require 'googleauth'
3
+ # require 'googleauth/stores/file_token_store'
4
+ require 'csv'
5
+
6
+ module DistributionWrappers
7
+ class Google < DistributionWrappers::Email
8
+
9
+ def send_message(recipient)
10
+ super
11
+
12
+ email = recipient['identifier']
13
+ client = get_access
14
+
15
+ msg = Mail.new
16
+ msg.date = Time.now
17
+ msg.subject = @params[:subject]
18
+ msg.body = @message
19
+ msg.to = email
20
+ msg.content_type = 'text/html'
21
+
22
+ if @params[:custom_opts]['send_from']
23
+ msg.from = @params[:custom_opts]['send_from']
24
+ end
25
+
26
+ send_response = client.send_user_message('me', upload_source: StringIO.new(msg.to_s), content_type: 'message/rfc822')
27
+ return {:response => send_response, :msg => msg}
28
+ end
29
+
30
+ def get_contacts
31
+ record_count = 0
32
+ loop do
33
+ url = "https://www.google.com/m8/feeds/contacts/default/full?max-results=10000&alt=json&start-index=#{record_count+1}"
34
+ response = JSON.parse(RestClient.get(url, {"GData-Version" => "3.0", "Authorization" => "Bearer #{@auth[:key]}"}))
35
+
36
+ break if response['feed'].nil? || response['feed']['entry'].nil?
37
+
38
+ response['feed']['entry'].each_with_index do |entry, index|
39
+ new_string = convert_to_contact_entry(entry, index)
40
+ csv_string += new_string unless new_string.nil?
41
+ if index % 100 == 0
42
+ @params[:temp_file].write(csv_string)
43
+ csv_string = ""
44
+ end
45
+ end
46
+
47
+ @params[:temp_file].write(csv_string)
48
+ csv_string = ""
49
+
50
+ record_count += response['feed']['entry'].length
51
+ break if response['feed']['entry'].length < 10000
52
+ end
53
+
54
+ @params[:temp_file].rewind
55
+ return true
56
+ end
57
+
58
+ def convert_to_contact_entry(entry, index)
59
+ # creating nil fields to keep the fields consistent across other networks
60
+ name = ""
61
+ if entry['gd$name']
62
+ gd_name = entry['gd$name']
63
+ first_name = normalize_name(entry['gd$name']['gd$givenName']['$t']) if gd_name['gd$givenName']
64
+ last_name = normalize_name(entry['gd$name']['gd$familyName']['$t']) if gd_name['gd$familyName']
65
+ name = normalize_name(entry['gd$name']['gd$fullName']['$t']) if gd_name['gd$fullName']
66
+ name = full_name(first_name,last_name) if name.nil?
67
+ end
68
+
69
+ emails = []
70
+ entry['gd$email'].each do |email|
71
+ if email['rel']
72
+ split_index = email['rel'].index('#')
73
+ emails << {:name => email['rel'][split_index + 1, email['rel'].length - 1], :email => email['address']}
74
+ elsif email['label']
75
+ emails << {:name => email['label'], :email => email['address']}
76
+ end
77
+ end if entry['gd$email']
78
+
79
+ # Support older versions of the gem by keeping singular entries around
80
+ email = emails[0][:email] if emails[0]
81
+
82
+ name = email_to_name(name) if (!name.nil? && name.include?('@'))
83
+ name = email_to_name(email) if (name.nil? && emails[0] && email)
84
+
85
+ if email && email != "" && name && name != ""
86
+ return_string = CSV.generate_line [name, email, '{"url": null}', 'email', @params[:channel_id]]
87
+ else
88
+ return_string = nil
89
+ end
90
+
91
+ return_string
92
+ end
93
+
94
+ def get_access
95
+ service = Google::Google::Apis::GmailV1::GmailService.new
96
+ service.authorization = @auth[:key]
97
+ service
98
+ end
99
+
100
+ # normalize the name
101
+ def normalize_name name
102
+ return nil if name.nil?
103
+ name.chomp!
104
+ name.squeeze!(' ')
105
+ name.strip!
106
+ return name
107
+ end
108
+
109
+ # create a full name given the individual first and last name
110
+ def full_name first_name, last_name
111
+ return "#{first_name} #{last_name}" if first_name && last_name
112
+ return "#{first_name}" if first_name && !last_name
113
+ return "#{last_name}" if !first_name && last_name
114
+ return nil
115
+ end
116
+
117
+ # create a username/name from a given email
118
+ def email_to_name username_or_email
119
+ username_or_email = username_or_email.split('@').first if username_or_email.include?('@')
120
+ if group = (/(?<first>[a-z|A-Z]+)[\.|_](?<last>[a-z|A-Z]+)/).match(username_or_email)
121
+ first_name = normalize_name(group[:first])
122
+ last_name = normalize_name(group[:last])
123
+ return "#{first_name} #{last_name}"
124
+ end
125
+ username = normalize_name(username_or_email)
126
+ return username
127
+ end
128
+ end
129
+ end