mailhandler 1.0.36 → 1.0.37
Sign up to get free protection for your applications and to get access to all the features.
- 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
|