mailhandler 1.0.38 → 1.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|