mailhandler 1.0.38 → 1.0.39
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop.yml +26 -0
- data/Gemfile +2 -0
- data/lib/mailhandler.rb +2 -10
- data/lib/mailhandler/errors.rb +1 -0
- data/lib/mailhandler/receiver.rb +14 -6
- data/lib/mailhandler/receiving/base.rb +28 -22
- data/lib/mailhandler/receiving/filelist/base.rb +17 -6
- data/lib/mailhandler/receiving/filelist/filter/base.rb +8 -3
- data/lib/mailhandler/receiving/filelist/filter/email.rb +3 -0
- data/lib/mailhandler/receiving/folder.rb +8 -3
- data/lib/mailhandler/receiving/imap.rb +53 -51
- data/lib/mailhandler/receiving/mail.rb +6 -13
- data/lib/mailhandler/receiving/notification/console.rb +2 -1
- data/lib/mailhandler/receiving/notification/email.rb +7 -4
- data/lib/mailhandler/receiving/notification/email/content.rb +1 -4
- data/lib/mailhandler/receiving/notification/email/states.rb +23 -20
- data/lib/mailhandler/receiving/observer.rb +2 -1
- data/lib/mailhandler/sender.rb +1 -1
- data/lib/mailhandler/sending/api.rb +3 -3
- data/lib/mailhandler/sending/api_batch.rb +4 -1
- data/lib/mailhandler/sending/base.rb +4 -1
- data/lib/mailhandler/sending/smtp.rb +0 -1
- data/lib/mailhandler/version.rb +1 -1
- data/mailhandler.gemspec +1 -2
- data/spec/unit/mailhandler/receiver_spec.rb +23 -15
- data/spec/unit/mailhandler/receiving/base_spec.rb +8 -7
- data/spec/unit/mailhandler/receiving/folder_spec.rb +42 -29
- data/spec/unit/mailhandler/receiving/imap_spec.rb +4 -5
- data/spec/unit/mailhandler/receiving/notification/console_spec.rb +5 -5
- data/spec/unit/mailhandler/receiving/notification/email/content_spec.rb +22 -18
- data/spec/unit/mailhandler/receiving/notification/email_spec.rb +12 -12
- data/spec/unit/mailhandler/sender_spec.rb +7 -3
- data/spec/unit/mailhandler/sending/sender_api_batch_spec.rb +9 -7
- data/spec/unit/mailhandler/sending/sender_api_spec.rb +4 -4
- data/spec/unit/mailhandler/sending/sender_smtp_spec.rb +7 -7
- data/spec/unit/mailhandler_spec.rb +10 -9
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7110dbb8ad938a31bdb9b4364e2cca1938aa1c9db573fc71acc00381946104a3
|
4
|
+
data.tar.gz: 60f583b519a3794cbca7c0efabc396808754557875c345d763d0fb28c9e182f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a2af60b8ace6be532020704039cd57e38cc9a01af02fd0677936c96fc36fdc427c6836b958d91ad2786d0701880464702c563f8f44e032b4827b714193af6c7
|
7
|
+
data.tar.gz: 268ce1771328c25865e6c3059faf4411326f139170956a3be3a476330037ec712597fa5308390b93dc02769af047b0e339cccbfc8beaba0810b1d59cdfa4a5ef
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require: rubocop-rspec
|
2
|
+
|
3
|
+
# increase line length since we are checking test files
|
4
|
+
Metrics/LineLength:
|
5
|
+
Max: 120
|
6
|
+
|
7
|
+
Metrics/MethodLength:
|
8
|
+
Max: 15
|
9
|
+
|
10
|
+
Metrics/ClassLength:
|
11
|
+
Max: 120
|
12
|
+
|
13
|
+
RSpec/ContextWording:
|
14
|
+
Enabled: false
|
15
|
+
|
16
|
+
RSpec/NestedGroups:
|
17
|
+
Max: 7
|
18
|
+
|
19
|
+
Metrics/BlockLength:
|
20
|
+
Max: 200
|
21
|
+
|
22
|
+
RSpec/FilePath:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
RSpec/ExampleLength:
|
26
|
+
Max: 10
|
data/Gemfile
CHANGED
data/lib/mailhandler.rb
CHANGED
@@ -71,11 +71,9 @@ module MailHandler
|
|
71
71
|
# @param [Receiving::Object] receiver
|
72
72
|
# @param [Array<Receiving::Notification::Class>] notifications
|
73
73
|
def add_receiving_notifications(receiver, notifications)
|
74
|
-
|
74
|
+
return unless (notifications - NOTIFICATION_TYPES.keys).empty?
|
75
75
|
|
76
|
-
|
77
|
-
|
78
|
-
end
|
76
|
+
notifications.each { |n| receiver.add_observer(NOTIFICATION_TYPES[n].new) }
|
79
77
|
end
|
80
78
|
|
81
79
|
def verify_type(type, types)
|
@@ -83,25 +81,19 @@ module MailHandler
|
|
83
81
|
end
|
84
82
|
|
85
83
|
CHECKER_TYPES = {
|
86
|
-
|
87
84
|
folder: Receiving::FolderChecker,
|
88
85
|
imap: Receiving::IMAPChecker
|
89
|
-
|
90
86
|
}.freeze
|
91
87
|
|
92
88
|
SENDER_TYPES = {
|
93
|
-
|
94
89
|
postmark_api: Sending::PostmarkAPISender,
|
95
90
|
postmark_batch_api: Sending::PostmarkBatchAPISender,
|
96
91
|
smtp: Sending::SMTPSender
|
97
|
-
|
98
92
|
}.freeze
|
99
93
|
|
100
94
|
NOTIFICATION_TYPES = {
|
101
|
-
|
102
95
|
console: Receiving::Notification::Console,
|
103
96
|
email: Receiving::Notification::Email
|
104
|
-
|
105
97
|
}.freeze
|
106
98
|
end
|
107
99
|
end
|
data/lib/mailhandler/errors.rb
CHANGED
data/lib/mailhandler/receiver.rb
CHANGED
@@ -4,6 +4,7 @@ require_relative 'receiving/observer'
|
|
4
4
|
require_relative 'receiving/mail.rb'
|
5
5
|
|
6
6
|
module MailHandler
|
7
|
+
# handling receiving email
|
7
8
|
class Receiver
|
8
9
|
include Receiving::Observer
|
9
10
|
|
@@ -40,13 +41,9 @@ module MailHandler
|
|
40
41
|
checker.start
|
41
42
|
|
42
43
|
until search_time_expired?
|
44
|
+
break if single_search(options)
|
43
45
|
|
44
|
-
received = checker.find(options)
|
45
|
-
update_search_details
|
46
|
-
notify_observers(search)
|
47
|
-
break if received
|
48
46
|
sleep search_frequency
|
49
|
-
|
50
47
|
end
|
51
48
|
|
52
49
|
notify_observers(search)
|
@@ -57,6 +54,13 @@ module MailHandler
|
|
57
54
|
|
58
55
|
private
|
59
56
|
|
57
|
+
def single_search(options)
|
58
|
+
received = checker.find(options)
|
59
|
+
update_search_details
|
60
|
+
notify_observers(search)
|
61
|
+
received
|
62
|
+
end
|
63
|
+
|
60
64
|
def init_search_details(options)
|
61
65
|
@search = Search.new
|
62
66
|
@search.options = options
|
@@ -68,8 +72,12 @@ module MailHandler
|
|
68
72
|
search.finished_at = Time.now
|
69
73
|
search.duration = search.finished_at - search.started_at
|
70
74
|
search.result = checker.search_result
|
75
|
+
update_search_email_details
|
76
|
+
end
|
77
|
+
|
78
|
+
def update_search_email_details
|
71
79
|
search.emails = checker.found_emails
|
72
|
-
search.email =
|
80
|
+
search.email = checker.found_emails.first
|
73
81
|
end
|
74
82
|
|
75
83
|
def search_time_expired?
|
@@ -33,10 +33,7 @@ module MailHandler
|
|
33
33
|
@found_emails = []
|
34
34
|
end
|
35
35
|
|
36
|
-
private
|
37
|
-
|
38
36
|
AVAILABLE_SEARCH_OPTIONS = %i[
|
39
|
-
|
40
37
|
by_subject
|
41
38
|
by_content
|
42
39
|
since
|
@@ -59,36 +56,45 @@ module MailHandler
|
|
59
56
|
end
|
60
57
|
|
61
58
|
def validate_option_values(options)
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
unless options[:count].nil?
|
59
|
+
validate_since_option(options)
|
60
|
+
validate_count_option(options)
|
61
|
+
validate_archive_option(options)
|
62
|
+
validate_recipient_option(options)
|
63
|
+
end
|
69
64
|
|
70
|
-
|
71
|
-
|
65
|
+
def validate_recipient_option(options)
|
66
|
+
return if options[:by_recipient].nil?
|
72
67
|
|
73
|
-
|
68
|
+
error_message = "Incorrect option options[:by_recipient]=#{options[:by_recipient]}."
|
69
|
+
raise MailHandler::Error, error_message unless options[:by_recipient].is_a?(Hash)
|
70
|
+
end
|
74
71
|
|
75
|
-
|
72
|
+
def validate_archive_option(options)
|
73
|
+
return if options[:archive].nil?
|
76
74
|
|
77
|
-
|
75
|
+
error_message = "Incorrect option options[:archive]=#{options[:archive]}."
|
76
|
+
raise MailHandler::Error, error_message unless [true, false].include?(options[:archive])
|
77
|
+
end
|
78
78
|
|
79
|
-
|
79
|
+
def validate_since_option(options)
|
80
|
+
return if options[:since].nil?
|
80
81
|
|
81
|
-
|
82
|
+
error_message = "Incorrect option options[:since]=#{options[:since]}."
|
83
|
+
raise MailHandler::Error, error_message unless options[:since].is_a?(Time)
|
84
|
+
end
|
82
85
|
|
83
|
-
|
86
|
+
def validate_count_option(options)
|
87
|
+
return if options[:count].nil?
|
84
88
|
|
85
|
-
|
89
|
+
count = options[:count]
|
90
|
+
error_message = "Incorrect option options[:count]=#{options[:count]}."
|
91
|
+
raise MailHandler::Error, error_message if (count < 0) || (count > 2000)
|
86
92
|
end
|
87
93
|
|
88
94
|
def validate_used_options(options)
|
89
|
-
|
90
|
-
|
91
|
-
|
95
|
+
error_message = "#{(options.keys - available_search_options)} - Incorrect search option values,"\
|
96
|
+
" options are #{available_search_options}."
|
97
|
+
raise MailHandler::Error, error_message unless (options.keys - available_search_options).empty?
|
92
98
|
end
|
93
99
|
|
94
100
|
def set_base_search_options
|
@@ -2,9 +2,10 @@ require 'fileutils'
|
|
2
2
|
|
3
3
|
# Base filtering class, which is used for reading list of all files based on passed pattern.
|
4
4
|
# Patterns to be used can be checked here: http://ruby-doc.org/core-1.9.3/Dir.html
|
5
|
-
|
6
5
|
module MailHandler
|
6
|
+
# namespace
|
7
7
|
module Receiving
|
8
|
+
# namespace
|
8
9
|
module FileHandling
|
9
10
|
# if file exists, execute file operation, otherwise return default return value when it doesn't
|
10
11
|
def access_file(file, default_return = false)
|
@@ -14,6 +15,7 @@ module MailHandler
|
|
14
15
|
yield
|
15
16
|
rescue StandardError => e
|
16
17
|
raise e if File.exist? file
|
18
|
+
|
17
19
|
default_return
|
18
20
|
end
|
19
21
|
|
@@ -25,6 +27,7 @@ module MailHandler
|
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
30
|
+
# base filelist
|
28
31
|
class FileList
|
29
32
|
include FileHandling
|
30
33
|
|
@@ -37,25 +40,33 @@ module MailHandler
|
|
37
40
|
j = 0
|
38
41
|
|
39
42
|
while swapped
|
40
|
-
|
41
43
|
swapped = false
|
42
44
|
j += 1
|
43
45
|
|
44
46
|
(files.size - j).times do |i|
|
45
|
-
|
46
|
-
file2 = access_file(files[i + 1], false) { File.new(files[i + 1]).ctime }
|
47
|
+
next unless swap_files?(files[i], files[i + 1])
|
47
48
|
|
48
|
-
next unless file1 && file2 && file1 < file2
|
49
49
|
tmp = files[i]
|
50
50
|
files[i] = files[i + 1]
|
51
51
|
files[i + 1] = tmp
|
52
52
|
swapped = true
|
53
53
|
end
|
54
|
-
|
55
54
|
end
|
56
55
|
|
57
56
|
files
|
58
57
|
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def swap_files?(current_file, next_file)
|
62
|
+
file1 = get_file(current_file)
|
63
|
+
file2 = get_file(next_file)
|
64
|
+
file1 && file2 && file1 < file2
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_file(file)
|
68
|
+
access_file(file, false) { File.new(file).ctime }
|
69
|
+
end
|
59
70
|
end
|
60
71
|
end
|
61
72
|
end
|
@@ -3,8 +3,11 @@ require_relative '../../../errors'
|
|
3
3
|
|
4
4
|
module MailHandler
|
5
5
|
module Receiving
|
6
|
+
# namespace
|
6
7
|
class FileList
|
8
|
+
# namespace
|
7
9
|
module Filter
|
10
|
+
# base filter for files
|
8
11
|
class Base
|
9
12
|
attr_accessor :files, :fast_check
|
10
13
|
|
@@ -36,7 +39,7 @@ module MailHandler
|
|
36
39
|
end
|
37
40
|
|
38
41
|
module ByDate
|
39
|
-
|
42
|
+
# filter files by date
|
40
43
|
class BaseDate < Base
|
41
44
|
def initialize(files, date)
|
42
45
|
super(files)
|
@@ -44,19 +47,21 @@ module MailHandler
|
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
50
|
+
# since date filter
|
47
51
|
class Since < BaseDate
|
48
52
|
private
|
49
53
|
|
50
54
|
def meets_expectation?(file)
|
51
|
-
File.exist?(file)? (File.ctime file) > @date : false
|
55
|
+
File.exist?(file) ? (File.ctime file) > @date : false
|
52
56
|
end
|
53
57
|
end
|
54
58
|
|
59
|
+
# before date filter
|
55
60
|
class Before < Base
|
56
61
|
private
|
57
62
|
|
58
63
|
def meets_expectation?(file)
|
59
|
-
File.exist?(file)? (File.ctime file) < @date : false
|
64
|
+
File.exist?(file) ? (File.ctime file) < @date : false
|
60
65
|
end
|
61
66
|
end
|
62
67
|
end
|
@@ -33,6 +33,7 @@ module MailHandler
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
# filter by email content
|
36
37
|
class ByEmailContent < Email
|
37
38
|
def initialize(files, content)
|
38
39
|
super(files)
|
@@ -60,6 +61,7 @@ module MailHandler
|
|
60
61
|
end
|
61
62
|
end
|
62
63
|
|
64
|
+
# filter by email subject
|
63
65
|
class ByEmailSubject < ByEmailContent
|
64
66
|
private
|
65
67
|
|
@@ -72,6 +74,7 @@ module MailHandler
|
|
72
74
|
end
|
73
75
|
end
|
74
76
|
|
77
|
+
# filter by email recipient
|
75
78
|
class ByEmailRecipient < Email
|
76
79
|
def initialize(files, recipient)
|
77
80
|
super(files)
|
@@ -7,6 +7,7 @@ require_relative 'filelist/filter/email.rb'
|
|
7
7
|
|
8
8
|
module MailHandler
|
9
9
|
module Receiving
|
10
|
+
# folder checking base class
|
10
11
|
class FolderChecker < Checker
|
11
12
|
include FileHandling
|
12
13
|
|
@@ -76,7 +77,7 @@ module MailHandler
|
|
76
77
|
end
|
77
78
|
|
78
79
|
def move_files(files)
|
79
|
-
files.each { |file|
|
80
|
+
files.each { |file| inbox_folder == archive_folder ? delete_file(file) : archive_file(file) }
|
80
81
|
end
|
81
82
|
|
82
83
|
def parse_email_from_files(files, count)
|
@@ -94,7 +95,10 @@ module MailHandler
|
|
94
95
|
end
|
95
96
|
|
96
97
|
def archive_file(file)
|
97
|
-
access_file(file)
|
98
|
+
access_file(file) do
|
99
|
+
FileUtils.mv("#{inbox_folder}/#{File.basename(file)}",
|
100
|
+
"#{archive_folder}/#{File.basename(file)}")
|
101
|
+
end
|
98
102
|
end
|
99
103
|
|
100
104
|
def delete_file(file)
|
@@ -103,7 +107,8 @@ module MailHandler
|
|
103
107
|
|
104
108
|
def verify_mailbox_folders
|
105
109
|
raise MailHandler::Error, 'Folder variables are not set.' if inbox_folder.nil? || archive_folder.nil?
|
106
|
-
raise MailHandler::FileError, 'Mailbox folders do not exist.' unless File.directory?(inbox_folder) &&
|
110
|
+
raise MailHandler::FileError, 'Mailbox folders do not exist.' unless File.directory?(inbox_folder) &&
|
111
|
+
File.directory?(archive_folder)
|
107
112
|
end
|
108
113
|
end
|
109
114
|
end
|
@@ -4,6 +4,7 @@ require_relative '../errors'
|
|
4
4
|
|
5
5
|
module MailHandler
|
6
6
|
module Receiving
|
7
|
+
# in charge of retrieving email by IMAP
|
7
8
|
class IMAPChecker < Checker
|
8
9
|
attr_accessor :address,
|
9
10
|
:port,
|
@@ -30,14 +31,16 @@ module MailHandler
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def start
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
return if manual_connection_manage
|
35
|
+
|
36
|
+
init_retriever
|
37
|
+
connect
|
37
38
|
end
|
38
39
|
|
39
40
|
def stop
|
40
|
-
|
41
|
+
return if manual_connection_manage
|
42
|
+
|
43
|
+
disconnect
|
41
44
|
end
|
42
45
|
|
43
46
|
def connect
|
@@ -45,21 +48,21 @@ module MailHandler
|
|
45
48
|
end
|
46
49
|
|
47
50
|
def disconnect
|
48
|
-
|
51
|
+
return if mailer.imap_connection.disconnected?
|
52
|
+
|
53
|
+
mailer.disconnect
|
49
54
|
end
|
50
55
|
|
51
56
|
# delegate retrieval details to Mail library
|
57
|
+
# set imap settings if they are not set
|
52
58
|
def init_retriever
|
53
|
-
|
54
|
-
unless retriever_set?
|
55
|
-
|
56
|
-
imap_settings = retriever_settings
|
59
|
+
return if retriever_set?
|
57
60
|
|
58
|
-
|
59
|
-
retriever_method :imap,
|
60
|
-
imap_settings
|
61
|
-
end
|
61
|
+
imap_settings = retriever_settings
|
62
62
|
|
63
|
+
Mail.defaults do
|
64
|
+
retriever_method :imap,
|
65
|
+
imap_settings
|
63
66
|
end
|
64
67
|
end
|
65
68
|
|
@@ -81,7 +84,6 @@ module MailHandler
|
|
81
84
|
# archive - Boolean
|
82
85
|
# by_recipient - Hash, accepts a hash like: :to => 'igor@example.com'
|
83
86
|
AVAILABLE_SEARCH_OPTIONS = %i[
|
84
|
-
|
85
87
|
by_subject
|
86
88
|
by_content
|
87
89
|
since
|
@@ -100,10 +102,8 @@ module MailHandler
|
|
100
102
|
|
101
103
|
def retriever_settings
|
102
104
|
{
|
103
|
-
address: address,
|
104
|
-
|
105
|
-
user_name: username,
|
106
|
-
password: password,
|
105
|
+
address: address, port: port,
|
106
|
+
user_name: username, password: password,
|
107
107
|
authentication: authentication,
|
108
108
|
enable_ssl: use_ssl
|
109
109
|
}
|
@@ -114,58 +114,60 @@ module MailHandler
|
|
114
114
|
end
|
115
115
|
|
116
116
|
def imap_search(retry_count, options)
|
117
|
-
result = mailer.find_emails(what: :last,
|
118
|
-
|
117
|
+
result = mailer.find_emails(what: :last,
|
118
|
+
count: search_options[:count],
|
119
|
+
order: :desc,
|
120
|
+
keys: imap_filter_keys(options),
|
121
|
+
delete_after_find: options[:archive])
|
122
|
+
result.is_a?(Array) ? result : [result]
|
119
123
|
|
120
124
|
# Silently ignore IMAP search errors, [RETRY_ON_ERROR_COUNT] times
|
121
125
|
rescue Net::IMAP::ResponseError, EOFError, NoMethodError => e
|
122
|
-
if (retry_count -= 1) >= 0
|
123
|
-
|
124
|
-
puts e
|
126
|
+
if (retry_count -= 1) >= 0 # rubocop:disable all
|
125
127
|
reconnect
|
126
128
|
retry
|
127
|
-
|
128
129
|
else
|
129
|
-
|
130
130
|
raise e
|
131
|
-
|
132
131
|
end
|
133
132
|
end
|
134
133
|
|
135
134
|
def imap_filter_keys(options)
|
136
135
|
keys = []
|
136
|
+
options.each { |key, value| keys += retrieve_filter_setting(key, value) }
|
137
|
+
keys.empty? ? nil : keys
|
138
|
+
end
|
137
139
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
keys << options[:by_recipient].keys.first.to_s.upcase << options[:by_recipient].values.first
|
144
|
-
|
145
|
-
when :by_subject
|
146
|
-
|
147
|
-
keys << 'SUBJECT' << options[:by_subject].to_s
|
148
|
-
|
149
|
-
when :by_content
|
150
|
-
|
151
|
-
keys << 'BODY' << options[:by_content].to_s
|
152
|
-
|
153
|
-
when :since
|
140
|
+
def retrieve_filter_setting(key, value)
|
141
|
+
case key
|
142
|
+
when :by_recipient
|
143
|
+
imap_recipient_search_pair(value)
|
154
144
|
|
155
|
-
|
145
|
+
when :by_subject
|
146
|
+
imap_string_search_pair('SUBJECT', value)
|
156
147
|
|
157
|
-
|
148
|
+
when :by_content
|
149
|
+
imap_string_search_pair('BODY', value)
|
158
150
|
|
159
|
-
|
151
|
+
when :since
|
152
|
+
imap_date_search_pair('SINCE', value)
|
160
153
|
|
161
|
-
|
154
|
+
when :before
|
155
|
+
imap_date_search_pair('BEFORE', value)
|
156
|
+
else
|
157
|
+
[]
|
158
|
+
end
|
159
|
+
end
|
162
160
|
|
163
|
-
|
161
|
+
def imap_recipient_search_pair(value)
|
162
|
+
[value.keys.first.to_s.upcase, value.values.first]
|
163
|
+
end
|
164
164
|
|
165
|
-
|
166
|
-
|
165
|
+
def imap_string_search_pair(name, value)
|
166
|
+
[name, value.to_s]
|
167
|
+
end
|
167
168
|
|
168
|
-
|
169
|
+
def imap_date_search_pair(name, value)
|
170
|
+
[name, Net::IMAP.format_date(value)]
|
169
171
|
end
|
170
172
|
end
|
171
173
|
end
|