mailhandler 1.0.36 → 1.0.37
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 +4 -4
- data/Gemfile +2 -2
- data/Rakefile +2 -2
- data/lib/mailhandler.rb +13 -37
- data/lib/mailhandler/errors.rb +1 -5
- data/lib/mailhandler/receiver.rb +4 -22
- data/lib/mailhandler/receiving/base.rb +16 -45
- data/lib/mailhandler/receiving/filelist/base.rb +25 -49
- data/lib/mailhandler/receiving/filelist/filter/base.rb +10 -52
- data/lib/mailhandler/receiving/filelist/filter/email.rb +4 -44
- data/lib/mailhandler/receiving/folder.rb +16 -54
- data/lib/mailhandler/receiving/imap.rb +34 -78
- data/lib/mailhandler/receiving/mail.rb +5 -19
- data/lib/mailhandler/receiving/notification/console.rb +2 -18
- data/lib/mailhandler/receiving/notification/email.rb +5 -28
- data/lib/mailhandler/receiving/notification/email/content.rb +9 -20
- data/lib/mailhandler/receiving/notification/email/states.rb +1 -36
- data/lib/mailhandler/receiving/observer.rb +5 -16
- data/lib/mailhandler/sender.rb +2 -14
- data/lib/mailhandler/sending/api.rb +1 -7
- data/lib/mailhandler/sending/api_batch.rb +1 -13
- data/lib/mailhandler/sending/base.rb +1 -13
- data/lib/mailhandler/sending/smtp.rb +20 -22
- data/lib/mailhandler/version.rb +2 -2
- data/mailhandler.gemspec +14 -17
- data/readme.md +33 -8
- data/spec/spec_helper.rb +1 -5
- data/spec/unit/mailhandler/receiver_spec.rb +8 -30
- data/spec/unit/mailhandler/receiving/base_spec.rb +4 -14
- data/spec/unit/mailhandler/receiving/folder_spec.rb +61 -155
- data/spec/unit/mailhandler/receiving/imap_spec.rb +18 -42
- data/spec/unit/mailhandler/receiving/notification/console_spec.rb +6 -16
- data/spec/unit/mailhandler/receiving/notification/email/content_spec.rb +10 -44
- data/spec/unit/mailhandler/receiving/notification/email_spec.rb +9 -15
- data/spec/unit/mailhandler/sender_spec.rb +12 -23
- data/spec/unit/mailhandler/sending/sender_api_batch_spec.rb +7 -19
- data/spec/unit/mailhandler/sending/sender_api_spec.rb +4 -14
- data/spec/unit/mailhandler/sending/sender_smtp_spec.rb +24 -6
- data/spec/unit/mailhandler_spec.rb +33 -25
- metadata +2 -2
@@ -2,107 +2,65 @@ require_relative '../base'
|
|
2
2
|
require_relative '../../../errors'
|
3
3
|
|
4
4
|
module MailHandler
|
5
|
-
|
6
5
|
module Receiving
|
7
|
-
|
8
6
|
class FileList
|
9
|
-
|
10
7
|
module Filter
|
11
|
-
|
12
8
|
class Base
|
13
|
-
|
14
9
|
attr_accessor :files, :fast_check
|
15
10
|
|
16
11
|
def initialize(files)
|
17
|
-
|
18
|
-
@files=files
|
19
|
-
|
12
|
+
@files = files
|
20
13
|
end
|
21
14
|
|
22
15
|
def get
|
23
|
-
|
24
16
|
files.select { |file| ignore_exception { meets_expectation?(file) } }
|
25
|
-
|
26
17
|
end
|
27
18
|
|
28
19
|
protected
|
29
20
|
|
30
|
-
def meet_expectation?(
|
31
|
-
|
21
|
+
def meet_expectation?(_file)
|
32
22
|
raise MailHandler::InterfaceError, 'Interface not implemented.'
|
33
|
-
|
34
23
|
end
|
35
24
|
|
36
25
|
def read_file(file)
|
37
|
-
|
38
26
|
File.read(file)
|
39
|
-
|
40
27
|
end
|
41
28
|
|
42
29
|
private
|
43
30
|
|
44
31
|
def ignore_exception
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
yield
|
49
|
-
|
50
|
-
rescue
|
51
|
-
|
52
|
-
false
|
53
|
-
|
54
|
-
end
|
55
|
-
|
32
|
+
yield
|
33
|
+
rescue StandardError
|
34
|
+
false
|
56
35
|
end
|
57
|
-
|
58
36
|
end
|
59
37
|
|
60
38
|
module ByDate
|
61
39
|
|
62
|
-
class
|
63
|
-
|
40
|
+
class BaseDate < Base
|
64
41
|
def initialize(files, date)
|
65
|
-
|
66
42
|
super(files)
|
67
43
|
@date = date
|
68
|
-
|
69
44
|
end
|
45
|
+
end
|
70
46
|
|
47
|
+
class Since < BaseDate
|
71
48
|
private
|
72
49
|
|
73
50
|
def meets_expectation?(file)
|
74
|
-
|
75
|
-
(File.exists? file)? (File.ctime file) > @date : false
|
76
|
-
|
51
|
+
File.exist?(file)? (File.ctime file) > @date : false
|
77
52
|
end
|
78
|
-
|
79
53
|
end
|
80
54
|
|
81
55
|
class Before < Base
|
82
|
-
|
83
|
-
def initialize(files, date)
|
84
|
-
|
85
|
-
super(files)
|
86
|
-
@date = date
|
87
|
-
|
88
|
-
end
|
89
|
-
|
90
56
|
private
|
91
57
|
|
92
58
|
def meets_expectation?(file)
|
93
|
-
|
94
|
-
(File.exists? file)? (File.ctime file) < @date : false
|
95
|
-
|
59
|
+
File.exist?(file)? (File.ctime file) < @date : false
|
96
60
|
end
|
97
|
-
|
98
61
|
end
|
99
|
-
|
100
62
|
end
|
101
|
-
|
102
63
|
end
|
103
|
-
|
104
64
|
end
|
105
|
-
|
106
65
|
end
|
107
|
-
|
108
66
|
end
|
@@ -2,72 +2,50 @@ require 'mail'
|
|
2
2
|
require_relative 'base'
|
3
3
|
|
4
4
|
module MailHandler
|
5
|
-
|
6
5
|
module Receiving
|
7
|
-
|
8
6
|
class FileList
|
9
|
-
|
10
7
|
module Filter
|
11
|
-
|
12
8
|
# filtering file content by its email properties
|
13
9
|
class Email < Base
|
14
|
-
|
15
10
|
def initialize(files)
|
16
|
-
|
17
|
-
@fast_check=true
|
11
|
+
@fast_check = true
|
18
12
|
super(files)
|
19
|
-
|
20
13
|
end
|
21
14
|
|
22
15
|
protected
|
23
16
|
|
24
17
|
def read_email_from_file(file)
|
25
|
-
|
26
18
|
Mail.read(file)
|
27
|
-
|
28
19
|
end
|
29
20
|
|
30
21
|
def meets_expectation?(file)
|
31
|
-
|
32
22
|
# fast content checks search for content by file reading
|
33
23
|
# slow content checks search for content by reconstructing email from file and then searching for content
|
34
|
-
|
35
|
-
|
24
|
+
fast_check ? check_content_fast(file) : check_content_slow(file)
|
36
25
|
end
|
37
26
|
|
38
|
-
def check_content_fast(
|
39
|
-
|
27
|
+
def check_content_fast(_file)
|
40
28
|
raise MailHandler::InterfaceError, 'Interface not implemented.'
|
41
|
-
|
42
29
|
end
|
43
30
|
|
44
|
-
def check_content_slow(
|
45
|
-
|
31
|
+
def check_content_slow(_file)
|
46
32
|
raise MailHandler::InterfaceError, 'Interface not implemented.'
|
47
|
-
|
48
33
|
end
|
49
|
-
|
50
34
|
end
|
51
35
|
|
52
36
|
class ByEmailContent < Email
|
53
|
-
|
54
37
|
def initialize(files, content)
|
55
|
-
|
56
38
|
super(files)
|
57
39
|
@content = content
|
58
|
-
|
59
40
|
end
|
60
41
|
|
61
42
|
private
|
62
43
|
|
63
44
|
def check_content_fast(file)
|
64
|
-
|
65
45
|
read_file(file).include? @content
|
66
|
-
|
67
46
|
end
|
68
47
|
|
69
48
|
def check_content_slow(file)
|
70
|
-
|
71
49
|
email = read_email_from_file(file)
|
72
50
|
|
73
51
|
if email.multipart?
|
@@ -79,52 +57,34 @@ module MailHandler
|
|
79
57
|
email.decoded.include? @content
|
80
58
|
|
81
59
|
end
|
82
|
-
|
83
60
|
end
|
84
|
-
|
85
61
|
end
|
86
62
|
|
87
63
|
class ByEmailSubject < ByEmailContent
|
88
|
-
|
89
64
|
private
|
90
65
|
|
91
66
|
def check_content_fast(file)
|
92
|
-
|
93
67
|
read_file(file).include? @content
|
94
|
-
|
95
68
|
end
|
96
69
|
|
97
70
|
def check_content_slow(file)
|
98
|
-
|
99
71
|
read_email_from_file(file).subject.to_s.include? @content
|
100
|
-
|
101
72
|
end
|
102
|
-
|
103
73
|
end
|
104
74
|
|
105
75
|
class ByEmailRecipient < Email
|
106
|
-
|
107
76
|
def initialize(files, recipient)
|
108
|
-
|
109
77
|
super(files)
|
110
78
|
@recipient = recipient
|
111
|
-
|
112
79
|
end
|
113
80
|
|
114
81
|
private
|
115
82
|
|
116
83
|
def meets_expectation?(file)
|
117
|
-
|
118
84
|
read_email_from_file(file)[@recipient.keys.first].to_s.include? @recipient.values.first
|
119
|
-
|
120
85
|
end
|
121
|
-
|
122
86
|
end
|
123
|
-
|
124
87
|
end
|
125
|
-
|
126
88
|
end
|
127
|
-
|
128
89
|
end
|
129
|
-
|
130
90
|
end
|
@@ -6,11 +6,8 @@ require_relative 'filelist/filter/base.rb'
|
|
6
6
|
require_relative 'filelist/filter/email.rb'
|
7
7
|
|
8
8
|
module MailHandler
|
9
|
-
|
10
9
|
module Receiving
|
11
|
-
|
12
10
|
class FolderChecker < Checker
|
13
|
-
|
14
11
|
include FileHandling
|
15
12
|
|
16
13
|
# folders in which emails will be searched for and managed
|
@@ -18,36 +15,28 @@ module MailHandler
|
|
18
15
|
:archive_folder
|
19
16
|
|
20
17
|
def initialize(inbox_folder = nil, archive_folder = nil)
|
21
|
-
|
22
18
|
super()
|
23
19
|
|
24
20
|
@inbox_folder = inbox_folder
|
25
21
|
@archive_folder = archive_folder
|
26
|
-
|
27
22
|
end
|
28
23
|
|
29
24
|
# check whether email is received by checking for an email in folder
|
30
25
|
def find(options)
|
31
|
-
|
32
26
|
verify_and_set_search_options(options)
|
33
27
|
verify_mailbox_folders
|
34
28
|
email_files = find_files(search_options)
|
35
29
|
|
36
30
|
unless email_files.empty?
|
37
|
-
|
38
31
|
@found_emails = parse_email_from_files(email_files, search_options[:count])
|
39
32
|
move_files(email_files) if search_options[:archive]
|
40
|
-
|
41
33
|
end
|
42
34
|
|
43
35
|
search_result
|
44
|
-
|
45
36
|
end
|
46
37
|
|
47
38
|
def start
|
48
|
-
|
49
39
|
verify_mailbox_folders
|
50
|
-
|
51
40
|
end
|
52
41
|
|
53
42
|
private
|
@@ -55,94 +44,67 @@ module MailHandler
|
|
55
44
|
# filter options which need to be done by searching files
|
56
45
|
FILE_SEARCH_CLASSES = {
|
57
46
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
}
|
47
|
+
by_subject: FileList::Filter::ByEmailSubject,
|
48
|
+
by_content: FileList::Filter::ByEmailContent,
|
49
|
+
since: FileList::Filter::ByDate::Since,
|
50
|
+
before: FileList::Filter::ByDate::Before,
|
51
|
+
by_recipient: FileList::Filter::ByEmailRecipient
|
52
|
+
}.freeze
|
64
53
|
|
65
54
|
def search_pattern
|
66
|
-
|
67
55
|
@inbox_folder + '/*.*'
|
68
|
-
|
69
56
|
end
|
70
57
|
|
71
58
|
# find files by FILE_SEARCH_CLASSES options
|
72
59
|
# this will ignore filter criteria options which can't be done on files directly
|
73
60
|
def find_files(options)
|
74
|
-
|
75
61
|
file_list = FileList.new
|
76
62
|
files = filter_files(file_list.get(search_pattern), options)
|
77
63
|
file_list.sort(files)
|
78
|
-
|
79
64
|
end
|
80
65
|
|
81
66
|
def filter_files(files, options)
|
82
|
-
|
83
67
|
options.each do |key, value|
|
68
|
+
next if FILE_SEARCH_CLASSES[key].nil?
|
84
69
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
filter.fast_check = options[:fast_check] unless options[:fast_check].nil?
|
89
|
-
files = filter.get
|
90
|
-
|
91
|
-
end
|
92
|
-
|
70
|
+
filter = FILE_SEARCH_CLASSES[key].new(files, value)
|
71
|
+
filter.fast_check = options[:fast_check] unless options[:fast_check].nil?
|
72
|
+
files = filter.get
|
93
73
|
end
|
94
74
|
|
95
75
|
files
|
96
|
-
|
97
76
|
end
|
98
77
|
|
99
78
|
def move_files(files)
|
100
|
-
|
101
|
-
files.each { |file| (inbox_folder == archive_folder)? delete_file(file) : archive_file(file) }
|
102
|
-
|
79
|
+
files.each { |file| (inbox_folder == archive_folder) ? delete_file(file) : archive_file(file) }
|
103
80
|
end
|
104
81
|
|
105
82
|
def parse_email_from_files(files, count)
|
106
|
-
|
107
83
|
read_files(files, count).map { |email_string| Mail.read_from_string(email_string) }
|
108
|
-
|
109
84
|
end
|
110
85
|
|
111
86
|
def read_files(files, count)
|
112
|
-
|
113
87
|
file_contents = []
|
114
88
|
files.first(count).each do |file|
|
115
|
-
|
116
|
-
file_content = access_file(file, nil) { File.read(file ) }
|
89
|
+
file_content = access_file(file, nil) { File.read(file) }
|
117
90
|
file_contents << file_content unless file_content.nil?
|
118
|
-
|
119
91
|
end
|
120
92
|
|
121
93
|
file_contents
|
122
|
-
|
123
94
|
end
|
124
95
|
|
125
96
|
def archive_file(file)
|
126
|
-
|
127
97
|
access_file(file) { FileUtils.mv("#{inbox_folder}/#{File.basename(file)}", "#{archive_folder}/#{File.basename(file)}") }
|
128
|
-
|
129
98
|
end
|
130
99
|
|
131
100
|
def delete_file(file)
|
132
|
-
|
133
|
-
access_file(file) { FileUtils.rm_r "#{inbox_folder}/#{File.basename(file)}", :force => false }
|
134
|
-
|
101
|
+
access_file(file) { FileUtils.rm_r "#{inbox_folder}/#{File.basename(file)}", force: false }
|
135
102
|
end
|
136
103
|
|
137
104
|
def verify_mailbox_folders
|
138
|
-
|
139
|
-
raise MailHandler::
|
140
|
-
raise MailHandler::FileError, 'Mailbox folders do not exist.' unless File.directory? inbox_folder and File.directory? archive_folder
|
141
|
-
|
105
|
+
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) && File.directory?(archive_folder)
|
142
107
|
end
|
143
|
-
|
144
108
|
end
|
145
|
-
|
146
109
|
end
|
147
|
-
|
148
|
-
end
|
110
|
+
end
|
@@ -1,15 +1,10 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
require 'mail'
|
4
2
|
require_relative 'base.rb'
|
5
3
|
require_relative '../errors'
|
6
4
|
|
7
5
|
module MailHandler
|
8
|
-
|
9
6
|
module Receiving
|
10
|
-
|
11
7
|
class IMAPChecker < Checker
|
12
|
-
|
13
8
|
attr_accessor :address,
|
14
9
|
:port,
|
15
10
|
:username,
|
@@ -22,83 +17,61 @@ module MailHandler
|
|
22
17
|
attr_accessor :manual_connection_manage
|
23
18
|
|
24
19
|
def initialize
|
25
|
-
|
26
20
|
super
|
27
21
|
@manual_connection_manage = false
|
28
22
|
@available_search_options = AVAILABLE_SEARCH_OPTIONS
|
29
|
-
|
30
23
|
end
|
31
24
|
|
32
25
|
def find(options)
|
33
|
-
|
34
26
|
verify_and_set_search_options(options)
|
35
27
|
@found_emails = find_emails(search_options)
|
36
28
|
|
37
29
|
search_result
|
38
|
-
|
39
30
|
end
|
40
31
|
|
41
32
|
def start
|
42
|
-
|
43
33
|
unless manual_connection_manage
|
44
34
|
init_retriever
|
45
35
|
connect
|
46
36
|
end
|
47
|
-
|
48
37
|
end
|
49
38
|
|
50
39
|
def stop
|
51
|
-
|
52
|
-
unless manual_connection_manage
|
53
|
-
disconnect
|
54
|
-
end
|
55
|
-
|
40
|
+
disconnect unless manual_connection_manage
|
56
41
|
end
|
57
42
|
|
58
43
|
def connect
|
59
|
-
|
60
44
|
mailer.connect
|
61
|
-
|
62
45
|
end
|
63
46
|
|
64
47
|
def disconnect
|
65
|
-
|
66
48
|
mailer.disconnect unless mailer.imap_connection.disconnected?
|
67
|
-
|
68
49
|
end
|
69
50
|
|
70
51
|
# delegate retrieval details to Mail library
|
71
52
|
def init_retriever
|
72
|
-
|
73
53
|
# set imap settings if they are not set
|
74
54
|
unless retriever_set?
|
75
55
|
|
76
56
|
imap_settings = retriever_settings
|
77
57
|
|
78
58
|
Mail.defaults do
|
79
|
-
|
80
59
|
retriever_method :imap,
|
81
60
|
imap_settings
|
82
|
-
|
83
61
|
end
|
84
62
|
|
85
63
|
end
|
86
|
-
|
87
64
|
end
|
88
65
|
|
89
66
|
private
|
90
67
|
|
91
68
|
def mailer
|
92
|
-
|
93
69
|
@mailer ||= Mail.retriever_method
|
94
|
-
|
95
70
|
end
|
96
71
|
|
97
72
|
def reconnect
|
98
|
-
|
99
73
|
disconnect
|
100
74
|
connect
|
101
|
-
|
102
75
|
end
|
103
76
|
|
104
77
|
# search options:
|
@@ -107,55 +80,46 @@ module MailHandler
|
|
107
80
|
# count - Int, number of found emails to return
|
108
81
|
# archive - Boolean
|
109
82
|
# by_recipient - Hash, accepts a hash like: :to => 'igor@example.com'
|
110
|
-
AVAILABLE_SEARCH_OPTIONS = [
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
]
|
83
|
+
AVAILABLE_SEARCH_OPTIONS = %i[
|
84
|
+
|
85
|
+
by_subject
|
86
|
+
by_content
|
87
|
+
since
|
88
|
+
before
|
89
|
+
count
|
90
|
+
archive
|
91
|
+
by_recipient
|
92
|
+
fast_check
|
93
|
+
].freeze
|
122
94
|
|
123
95
|
RETRY_ON_ERROR_COUNT = 3
|
124
96
|
|
125
97
|
def retriever_set?
|
126
|
-
|
127
98
|
Mail.retriever_method.settings == retriever_settings
|
128
|
-
|
129
99
|
end
|
130
100
|
|
131
101
|
def retriever_settings
|
132
|
-
|
133
102
|
{
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
103
|
+
address: address,
|
104
|
+
port: port,
|
105
|
+
user_name: username,
|
106
|
+
password: password,
|
107
|
+
authentication: authentication,
|
108
|
+
enable_ssl: use_ssl
|
140
109
|
}
|
141
|
-
|
142
110
|
end
|
143
111
|
|
144
112
|
def find_emails(options)
|
145
|
-
|
146
113
|
imap_search(RETRY_ON_ERROR_COUNT, options)
|
147
|
-
|
148
114
|
end
|
149
115
|
|
150
116
|
def imap_search(retry_count, options)
|
151
|
-
|
152
|
-
result
|
153
|
-
(result.kind_of? Array)? result : [result]
|
117
|
+
result = mailer.find_emails(what: :last, count: search_options[:count], order: :desc, keys: imap_filter_keys(options), delete_after_find: options[:archive])
|
118
|
+
result.is_a? Array ? result : [result]
|
154
119
|
|
155
120
|
# Silently ignore IMAP search errors, [RETRY_ON_ERROR_COUNT] times
|
156
121
|
rescue Net::IMAP::ResponseError, EOFError, NoMethodError => e
|
157
|
-
|
158
|
-
if (retry_count -=1) >= 0
|
122
|
+
if (retry_count -= 1) >= 0
|
159
123
|
|
160
124
|
puts e
|
161
125
|
reconnect
|
@@ -166,51 +130,43 @@ module MailHandler
|
|
166
130
|
raise e
|
167
131
|
|
168
132
|
end
|
169
|
-
|
170
133
|
end
|
171
134
|
|
172
135
|
def imap_filter_keys(options)
|
173
|
-
|
174
136
|
keys = []
|
175
137
|
|
176
138
|
options.keys.each do |filter_option|
|
177
|
-
|
178
139
|
case filter_option
|
179
140
|
|
180
|
-
|
141
|
+
when :by_recipient
|
181
142
|
|
182
|
-
|
143
|
+
keys << options[:by_recipient].keys.first.to_s.upcase << options[:by_recipient].values.first
|
183
144
|
|
184
|
-
|
145
|
+
when :by_subject
|
185
146
|
|
186
|
-
|
147
|
+
keys << 'SUBJECT' << options[:by_subject].to_s
|
187
148
|
|
188
|
-
|
149
|
+
when :by_content
|
189
150
|
|
190
|
-
|
151
|
+
keys << 'BODY' << options[:by_content].to_s
|
191
152
|
|
192
|
-
|
153
|
+
when :since
|
193
154
|
|
194
|
-
|
155
|
+
keys << 'SINCE' << Net::IMAP.format_date(options[:since])
|
195
156
|
|
196
|
-
|
157
|
+
when :before
|
197
158
|
|
198
|
-
|
159
|
+
keys << 'BEFORE' << Net::IMAP.format_date(options[:before])
|
199
160
|
|
200
|
-
|
161
|
+
else
|
201
162
|
|
202
|
-
|
163
|
+
# do nothing
|
203
164
|
|
204
165
|
end
|
205
|
-
|
206
166
|
end
|
207
167
|
|
208
|
-
|
209
|
-
|
168
|
+
keys.empty? ? nil : keys
|
210
169
|
end
|
211
|
-
|
212
170
|
end
|
213
|
-
|
214
171
|
end
|
215
|
-
|
216
172
|
end
|