cli_application 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,402 @@
1
+ # CliApplication::MailLib::Message - сборка сообщения электронной почты
2
+
3
+ module CliApplication
4
+ module MailLib
5
+ class Message
6
+ attr_reader :from_email
7
+ attr_accessor :from_name
8
+ attr_reader :reply_to
9
+ attr_accessor :subject
10
+ attr_accessor :charset
11
+ attr_accessor :body
12
+
13
+ # Конструктор инициализирует сообщение электронной почты и компоненты сообщения
14
+ #
15
+ # @return [None] нет
16
+ def initialize
17
+ @charset = 'utf-8'
18
+ @body = ''
19
+ @subject = ''
20
+ @from_email = ''
21
+ @from_name = ''
22
+ @reply_to = ''
23
+
24
+ @to = Hash.new
25
+ @cc = Hash.new
26
+ @bcc = Hash.new
27
+
28
+ @message_id = ::Time.now.to_s.hash.abs.to_s + '.' + ::Time.now.usec.to_s
29
+ end
30
+
31
+ # Метод осуществляет сборку (композицию) сообщения в формате MIME для записи в лог файл
32
+ # без преобразований base64
33
+ #
34
+ # @return [String] сообщение электронной почты в виде форматированного текста
35
+ def to_log
36
+ message = Array.new
37
+ message << "From: #{build_rfc822_name(@from_email, @from_name, false)}" unless @from_email == ''
38
+ message << build_to_adresses("To", @to, false)
39
+ message << build_to_adresses("Cc", @cc, false)
40
+ message << build_to_adresses("Bcc", @bcc, false)
41
+ message << "Reply-To: #{build_rfc822_name(@reply_to)}" unless @reply_to == ''
42
+ message << "Subject: #{@subject}"
43
+ message << html_to_text(@body.dup, 65, @charset)
44
+
45
+ message.compact!
46
+ message.join("\n")
47
+ end
48
+
49
+ # Метод осуществляет сборку (композицию) сообщения в формате MIME для отправки в Интернет.
50
+ # Поля TO, CC, BCC, Subject преобразуются в бинарную форму через base64
51
+ #
52
+ # @return [String] сообщение электронной почты в виде форматированного текста
53
+ def to_s
54
+ message = Array.new
55
+ message << "From: #{build_rfc822_name(@from_email, @from_name)}" unless @from_email == ''
56
+ message << "Return-Path: <#{@from_email}>" # http://maksd.info/blog/vse-posty-iz-starogo-bloga/message-75/
57
+ message << build_to_adresses("To", @to)
58
+ message << build_to_adresses("Cc", @cc)
59
+ message << build_to_adresses("Bcc", @bcc)
60
+ message << "Reply-To: #{build_rfc822_name(@reply_to)}" unless @reply_to == ''
61
+ message << "Subject: #{base64_string_encode(@subject)}"
62
+ message << "Date: #{::Time.zone.now.to_formatted_s(:rfc822) }"
63
+ message << "MIME-Version: 1.0 (Ruby gem cli_application; version #{::CliApplication::VERSION})"
64
+ message << "Message-ID: <#{@message_id + '@' + @from_email.split('@').last}>"
65
+ message += alternative_to_s
66
+ message += body_to_s(html_to_text(@body.dup, 65, @charset), 'text/plain')
67
+ message += body_to_s(@body, 'text/html')
68
+ message += footer_to_s
69
+
70
+ message.compact!
71
+ message.join("\n")
72
+ end
73
+
74
+ # Метод добавляет к сообщению указание на адрес и имя отправителя. Принимается формат вида
75
+ # "Name <name@host.ru>". При этом будет осуществлен корректный разбор строки на имя и адрес
76
+ #
77
+ # @param [String] val строка с адресом электронной почты
78
+ # @return [None] нет
79
+ # @example Примеры использования
80
+ # msg = CliApplication::MailLib::Message.new
81
+ # msg.from_email = "Name <user@host.ru>"
82
+ # msg.from_email #=> "user@host.ru"
83
+ # msg.from_name #=> "Name"
84
+ #
85
+ # msg.from_email = "user@host.ru"
86
+ # msg.from_email #=> "user@host.ru"
87
+ # msg.from_name #=> ""
88
+ def from_email=(val)
89
+ res = parse_email(val)
90
+ @from_name = res[:name]
91
+ @from_email = res[:email]
92
+ end
93
+
94
+ # Метод добавляет к сообщению указание на адрес для ответа. Принимается формат вида
95
+ # "Name <name@host.ru>". При этом будет осуществлен корректный разбор строки на имя и адрес
96
+ #
97
+ # @param [String] val строка с адресом электронной почты
98
+ # @return [None] нет
99
+ # @example Примеры использования
100
+ # msg = CliApplication::MailLib::Message.new
101
+ # msg.reply_to = "Name <user@host.ru>"
102
+ # msg.reply_to #=> "user@host.ru"
103
+ def reply_to=(val)
104
+ res = parse_email(val)
105
+ @reply_to = res[:email]
106
+ end
107
+
108
+ # Метод добавляет в поле TO получателя сообщения. Может вызываться несколько раз для
109
+ # добавления нескольких получателей. Особенности обработки. Если в метод передать значения адреса, включающего
110
+ # имя пользователя, то параметр name будет проигнорирован. Name будет взят из переданного адреса.
111
+ #
112
+ # @param [String] email адрес получателя в формате "user@host.ru" или "Name <user@host.ru>"
113
+ # @param [String] name имя пользователя
114
+ # @return [None] нет
115
+ # @example Примеры использования
116
+ # msg = CliApplication::MailLib::Message.new
117
+ # msg.add_to('user@host.ru', 'Name') #=> добавлено: "Name" и "user@host.ru"
118
+ # msg.add_to('USerName <user@host.ru>', 'Name') #=> добавлено: "UserName" и "user@host.ru"
119
+ def add_to(email, name = '')
120
+ res = parse_email(email)
121
+ if name == ''
122
+ @to[res[:email]] = res[:name]
123
+ else
124
+ @to[res[:email]] = name
125
+ end
126
+ end
127
+
128
+ # Метод добавляет в поле CC получателя сообщения. Может вызываться несколько раз для
129
+ # добавления нескольких получателей. Особенности обработки. Если в метод передать значения адреса, включающего
130
+ # имя пользователя, то параметр name будет проигнорирован. Name будет взят из переданного адреса. Вторая
131
+ # особенность - при использовании метода отправки :smtp, все CC-адреса будут помещены в TO.
132
+ #
133
+ # @param [String] email адрес получателя в формате "user@host.ru" или "Name <user@host.ru>"
134
+ # @param [String] name имя пользователя
135
+ # @return [None] нет
136
+ # @example Примеры использования
137
+ # msg = CliApplication::MailLib::Message.new
138
+ # msg.add_cc('user@host.ru', 'Name') #=> добавлено: "Name" и "user@host.ru"
139
+ # msg.add_cc('USerName <user@host.ru>', 'Name') #=> добавлено: "UserName" и "user@host.ru"
140
+ def add_cc(email, name = '')
141
+ res = parse_email(email)
142
+ if name == ''
143
+ @cc[res[:email]] = res[:name]
144
+ else
145
+ @cc[res[:email]] = name
146
+ end
147
+ end
148
+
149
+ # Метод добавляет в поле BCC получателя сообщения. Может вызываться несколько раз для
150
+ # добавления нескольких получателей. Особенности обработки. Если в метод передать значения адреса, включающего
151
+ # имя пользователя, то параметр name будет проигнорирован. Name будет взят из переданного адреса. Вторая
152
+ # особенность - при использовании метода отправки :smtp, все BCC-адреса будут удалены.
153
+ #
154
+ # @param [String] email адрес получателя в формате "user@host.ru" или "Name <user@host.ru>"
155
+ # @param [String] name имя пользователя
156
+ # @return [None] нет
157
+ # @example Примеры использования
158
+ # msg = CliApplication::MailLib::Message.new
159
+ # msg.add_bcc('user@host.ru', 'Name') #=> добавлено: "Name" и "user@host.ru"
160
+ # msg.add_bcc('USerName <user@host.ru>', 'Name') #=> добавлено: "UserName" и "user@host.ru"
161
+ def add_bcc(email, name = '')
162
+ res = parse_email(email)
163
+ if name == ''
164
+ @bcc[res[:email]] = res[:name]
165
+ else
166
+ @bcc[res[:email]] = name
167
+ end
168
+ end
169
+
170
+ # Метод очищает все ранее добавленные адреса TO.
171
+ #
172
+ # @param [Boolean] warning true для вывода предупреждения об удалении всех адресатов
173
+ # @return [None] нет
174
+ def clear_to(warning = false)
175
+ unless @to.empty?
176
+ if warning
177
+ warn "Предупреждение: TO-адреса #{@to.inspect} удалены"
178
+ end
179
+ @to = Hash.new
180
+ end
181
+ end
182
+
183
+ # Метод очищает все ранее добавленные адреса CC.
184
+ #
185
+ # @param [Boolean] warning true для вывода предупреждения об удалении всех адресатов
186
+ # @return [None] нет
187
+ def clear_cc(warning = false)
188
+ unless @cc.empty?
189
+ if warning
190
+ warn "Предупреждение: CC-адреса #{@cc.inspect} удалены"
191
+ end
192
+ @cc = Hash.new
193
+ end
194
+ end
195
+
196
+ # Метод очищает все ранее добавленные адреса BCC.
197
+ #
198
+ # @param [Boolean] warning true для вывода предупреждения об удалении всех адресатов
199
+ # @return [None] нет
200
+ def clear_bcc(warning = false)
201
+ unless @bcc.empty?
202
+ if warning
203
+ warn "Предупреждение: BCC-адреса #{@bcc.inspect} удалены"
204
+ end
205
+ @bcc = Hash.new
206
+ end
207
+ end
208
+
209
+
210
+
211
+ private
212
+
213
+
214
+
215
+ def to_emails # :nodoc:
216
+ out = build_to_adresses('', @to).gsub(/\A\:/, '')
217
+ out += ',' + build_to_adresses('', @cc).gsub(/\A\:/, '') unless @cc.empty?
218
+ StTools::String.split(out, ',')
219
+ end
220
+
221
+ def base64_string_encode(str) # :nodoc:
222
+ "=?UTF-8?B?" + Base64.strict_encode64(str) + "?="
223
+ end
224
+
225
+ def build_to_adresses(prefix, to, base64 = true) # :nodoc:
226
+ return nil if to.empty?
227
+
228
+ out = Array.new
229
+ to.each do |email, name|
230
+ out << build_rfc822_name(email, name, base64)
231
+ end
232
+
233
+ "#{prefix}: #{out.join(", ")}"
234
+ end
235
+
236
+ def build_rfc822_name(email, name, base64 = true) # :nodoc:
237
+ return "#{email}" if name.nil? || name == ''
238
+ if base64
239
+ "#{base64_string_encode(name)} <#{email}>"
240
+ else
241
+ "#{name.inspect} <#{email}>"
242
+ end
243
+ end
244
+
245
+ def parse_email(str) # :nodoc:
246
+ name = str.strip.match(/^.+\</)[0] rescue nil
247
+ if name.nil?
248
+ {name: '', email: str.strip}
249
+ else
250
+ {name: name.chomp('<').strip, email: str.strip.gsub(name, '').chomp('>')}
251
+ end
252
+ end
253
+
254
+ # https://github.com/premailer/premailer/blob/master/lib/premailer/html_to_plain_text.rb
255
+ def html_to_text(html, line_length = 65, from_charset = 'UTF-8') # :nodoc:
256
+ txt = html
257
+
258
+ # strip text ignored html. Useful for removing
259
+ # headers and footers that aren't needed in the
260
+ # text version
261
+ txt.gsub!(/<!-- start text\/html -->.*?<!-- end text\/html -->/m, '')
262
+
263
+ # replace images with their alt attributes
264
+ # for img tags with "" for attribute quotes
265
+ # with or without closing tag
266
+ # eg. the following formats:
267
+ # <img alt="" />
268
+ # <img alt="">
269
+ txt.gsub!(/<img.+?alt=\"([^\"]*)\"[^>]*\>/i, '\1')
270
+
271
+ # for img tags with '' for attribute quotes
272
+ # with or without closing tag
273
+ # eg. the following formats:
274
+ # <img alt='' />
275
+ # <img alt=''>
276
+ txt.gsub!(/<img.+?alt=\'([^\']*)\'[^>]*\>/i, '\1')
277
+
278
+ # links
279
+ txt.gsub!(/<a\s.*?href=\"(mailto:)?([^\"]*)\"[^>]*>((.|\s)*?)<\/a>/i) do |s|
280
+ if $3.empty?
281
+ ''
282
+ else
283
+ $3.strip + ' ( ' + $2.strip + ' )'
284
+ end
285
+ end
286
+
287
+ txt.gsub!(/<a\s.*?href='(mailto:)?([^\']*)\'[^>]*>((.|\s)*?)<\/a>/i) do |s|
288
+ if $3.empty?
289
+ ''
290
+ else
291
+ $3.strip + ' ( ' + $2.strip + ' )'
292
+ end
293
+ end
294
+
295
+ # handle headings (H1-H6)
296
+ txt.gsub!(/(<\/h[1-6]>)/i, "\n\\1") # move closing tags to new lines
297
+ txt.gsub!(/[\s]*<h([1-6]+)[^>]*>[\s]*(.*)[\s]*<\/h[1-6]+>/i) do |s|
298
+ hlevel = $1.to_i
299
+
300
+ htext = $2
301
+ htext.gsub!(/<br[\s]*\/?>/i, "\n") # handle <br>s
302
+ htext.gsub!(/<\/?[^>]*>/i, '') # strip tags
303
+
304
+ # determine maximum line length
305
+ hlength = 0
306
+ htext.each_line { |l| llength = l.strip.length; hlength = llength if llength > hlength }
307
+ hlength = line_length if hlength > line_length
308
+
309
+ case hlevel
310
+ when 1 # H1, asterisks above and below
311
+ htext = ('*' * hlength) + "\n" + htext + "\n" + ('*' * hlength)
312
+ when 2 # H1, dashes above and below
313
+ htext = ('-' * hlength) + "\n" + htext + "\n" + ('-' * hlength)
314
+ else # H3-H6, dashes below
315
+ htext = htext + "\n" + ('-' * hlength)
316
+ end
317
+
318
+ "\n\n" + htext + "\n\n"
319
+ end
320
+
321
+ # wrap spans
322
+ txt.gsub!(/(<\/span>)[\s]+(<span)/mi, '\1 \2')
323
+
324
+ # lists -- TODO: should handle ordered lists
325
+ txt.gsub!(/[\s]*(<li[^>]*>)[\s]*/i, '* ')
326
+ # list not followed by a newline
327
+ txt.gsub!(/<\/li>[\s]*(?![\n])/i, "\n")
328
+
329
+ # paragraphs and line breaks
330
+ txt.gsub!(/<\/p>/i, "\n\n")
331
+ txt.gsub!(/<br[\/ ]*>/i, "\n")
332
+
333
+ # strip remaining tags
334
+ txt.gsub!(/<\/?[^>]*>/, '')
335
+
336
+ # decode HTML entities
337
+ he = HTMLEntities.new
338
+ txt = he.decode(txt)
339
+
340
+ txt = word_wrap(txt, line_length)
341
+
342
+ # remove linefeeds (\r\n and \r -> \n)
343
+ txt.gsub!(/\r\n?/, "\n")
344
+
345
+ # strip extra spaces
346
+ txt.gsub!(/\302\240+/, " ") # non-breaking spaces -> spaces
347
+ txt.gsub!(/\n[ \t]+/, "\n") # space at start of lines
348
+ txt.gsub!(/[ \t]+\n/, "\n") # space at end of lines
349
+
350
+ # no more than two consecutive newlines
351
+ txt.gsub!(/[\n]{3,}/, "\n\n")
352
+
353
+ # no more than two consecutive spaces
354
+ txt.gsub!(/ {2,}/, " ")
355
+
356
+ # the word messes up the parens
357
+ txt.gsub!(/\([ \n](http[^)]+)[\n ]\)/) do |s|
358
+ "( " + $1 + " )"
359
+ end
360
+
361
+ txt.strip
362
+ end
363
+
364
+ # Taken from Rails' word_wrap helper (http://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-word_wrap)
365
+ def word_wrap(txt, line_length) # :nodoc:
366
+ txt.split("\n").collect do |line|
367
+ line.length > line_length ? line.gsub(/(.{1,#{line_length}})(\s+|$)/, "\\1\n").strip : line
368
+ end * "\n"
369
+ end
370
+
371
+ def boundary # :nodoc:
372
+ 'NextPart_' + @message_id.gsub(/\./, '_')
373
+ end
374
+
375
+ def alternative_to_s # :nodoc:
376
+ message = Array.new
377
+ message << "Content-Type: multipart/alternative; boundary=\"#{boundary}\""
378
+ message
379
+ end
380
+
381
+ def body_to_s(text, type) # :nodoc:
382
+ message = Array.new
383
+ message << ""
384
+ message << "--#{boundary}"
385
+ message << "Content-Transfer-Encoding: 8bit"
386
+ message << "Content-Type: #{type}; charset=\"#{@charset}\""
387
+ message << ""
388
+ message << text
389
+ message
390
+ end
391
+
392
+ def footer_to_s # :nodoc:
393
+ message = Array.new
394
+ message << ""
395
+ message << "--#{boundary}--"
396
+ message << ""
397
+ message
398
+ end
399
+
400
+ end
401
+ end
402
+ end
@@ -0,0 +1,89 @@
1
+ # CliApplication::MailLib::Sendmail - класс отправки электронной почты через Sendmail
2
+
3
+ module CliApplication
4
+ module MailLib
5
+ class Sendmail < ::CliApplication::MailLib::Base
6
+
7
+ def initialize(config, folders) # :nodoc:
8
+ @delivery_method = :sendmail
9
+ super(config, folders)
10
+
11
+ check_config
12
+ end
13
+
14
+ # Метод отправки электронного сообщения через sendmail
15
+ #
16
+ # @param [String] to электронная почта лица, которому отправляется сообщение, или массив адресов
17
+ # @param [String] name имя клиента, которому отправляется сообщение
18
+ # @param [String] title заголовок письма
19
+ # @param [String] body текст письма
20
+ # @return [Boolean] true, если письмо помещено в очередь сообщений sendmail
21
+ def simple_send(to, name, title, body)
22
+ return false unless valid?
23
+
24
+ message = CliApplication::MailLib::Message.new
25
+ message.from_email = @config.from
26
+ message.subject = title
27
+ message.body = (@config.footer.nil? || @config.footer == '') ? body : (body+@config.footer)
28
+ processing_to(to, name, message)
29
+
30
+ begin
31
+ send_message(message)
32
+ true
33
+ rescue Exception => e
34
+ $stderr.puts "Ошибка отправки письма: #{e.message}"
35
+ false
36
+ end
37
+ end
38
+
39
+ # Метод возвращает путь к скрипту 'sendmail'
40
+ #
41
+ # @return [String] путь к скрипту 'sendmail'
42
+ def sendmail_location
43
+ @config.sendmail.location
44
+ end
45
+
46
+ # Метод возаращает опции 'sendmail'
47
+ #
48
+ # @return [String] опции 'sendmail'
49
+ def sendmail_arguments
50
+ @config.sendmail.arguments
51
+ end
52
+
53
+
54
+ private
55
+
56
+
57
+
58
+ def send_message(message) # :nodoc:
59
+ # Дополнительная информация по использованию sendmail:
60
+ # http://blog.antage.name/posts/sendmail-in-rails.html
61
+
62
+ cmdline = Array.new
63
+ cmdline << "echo \"#{'rrrrr' + message.to_s}\""
64
+ cmdline << '|'
65
+ cmdline << sendmail_location
66
+ cmdline << sendmail_arguments
67
+ cmdline << '2>&1'
68
+ cmdline = cmdline.join(' ')
69
+
70
+ output = `#{cmdline}`
71
+ res = $?.exitstatus
72
+
73
+ if res != 0
74
+ raise "SendmailError_#{res}"
75
+ end
76
+ end
77
+
78
+ def check_config # :nodoc:
79
+ return set_check_config_state(false, "Не найдена секция конфиг-файла cli/mail/sendmail") if @config.sendmail.nil?
80
+ return set_check_config_state(false, "Не найден параметр конфиг-файла cli/mail/sendmail/location") if @config.sendmail.location.nil?
81
+ return set_check_config_state(false, "Не найден параметр конфиг-файла cli/mail/sendmail/arguments") if @config.sendmail.arguments.nil?
82
+ return set_check_config_state(false, "Не найден sendmail (#{@config.sendmail.location.inspect})") unless File.exist?(@config.sendmail.location)
83
+
84
+ set_check_config_state(true, '')
85
+ end
86
+
87
+ end
88
+ end
89
+ end