mailhandler 1.0.1
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 +7 -0
- data/.gitignore +5 -0
- data/lib/mailhandler.rb +91 -0
- data/lib/mailhandler/receiver.rb +86 -0
- data/lib/mailhandler/receiving/checker.rb +60 -0
- data/lib/mailhandler/receiving/checker_folder.rb +126 -0
- data/lib/mailhandler/receiving/checker_imap.rb +90 -0
- data/lib/mailhandler/receiving/filter.rb +161 -0
- data/lib/mailhandler/receiving/notification/console.rb +44 -0
- data/lib/mailhandler/receiving/notification/email.rb +74 -0
- data/lib/mailhandler/receiving/notification/email/content.rb +43 -0
- data/lib/mailhandler/receiving/notification/email/states.rb +113 -0
- data/lib/mailhandler/receiving/observer.rb +37 -0
- data/lib/mailhandler/sender.rb +56 -0
- data/lib/mailhandler/sending/sender.rb +13 -0
- data/lib/mailhandler/sending/sender_api.rb +45 -0
- data/lib/mailhandler/sending/sender_api_batch.rb +20 -0
- data/lib/mailhandler/sending/sender_smtp.rb +76 -0
- data/lib/mailhandler/version.rb +5 -0
- data/mailhandler.gemspec +28 -0
- data/readme.md +151 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ad4de21ce6ad278dd2916e5cb898691c10961b87
|
4
|
+
data.tar.gz: caedb45d153cbfa44d41b13328a9afb20c81b2fa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a0da923ce9b696ca05ce4cc1b322678bd272b44227ca25a330239375fac65c3bdddbe3fbc08b6b425a27b89c24907b4a48fd259a5a99cec88f5e4ac6f165f8c4
|
7
|
+
data.tar.gz: 7667ee7a964c55262104b954abd8289f7855b4107056041587702d76dbecab016425b139d49a3989ddcc53d7a905d781c029f39ca339cb122278d75542c46eae
|
data/lib/mailhandler.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require_relative 'mailhandler/sender'
|
2
|
+
require_relative 'mailhandler/receiver'
|
3
|
+
|
4
|
+
require_relative 'mailhandler/receiving/notification/email'
|
5
|
+
require_relative 'mailhandler/receiving/notification/console'
|
6
|
+
|
7
|
+
module MailHandler
|
8
|
+
|
9
|
+
class Handler
|
10
|
+
|
11
|
+
attr_accessor :sender,
|
12
|
+
:receiver
|
13
|
+
|
14
|
+
def self.sender(type = :postmark_api)
|
15
|
+
|
16
|
+
verify_type(type, SENDER_TYPES)
|
17
|
+
sender = MailHandler::Sender.new(SENDER_TYPES[type].new)
|
18
|
+
yield(sender.dispatcher) if block_given?
|
19
|
+
|
20
|
+
sender
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def self.receiver(type = :folder, notifications = [])
|
26
|
+
|
27
|
+
verify_type(type, CHECKER_TYPES)
|
28
|
+
receiver = MailHandler::Receiver.new(CHECKER_TYPES[type].new)
|
29
|
+
add_receiving_notifications(receiver, notifications)
|
30
|
+
|
31
|
+
yield(receiver.checker) if block_given?
|
32
|
+
|
33
|
+
receiver
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.handler(sender, receiver)
|
38
|
+
|
39
|
+
handler = new
|
40
|
+
handler.sender = sender
|
41
|
+
handler.receiver = receiver
|
42
|
+
handler
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def self.add_receiving_notifications(receiver, notifications)
|
49
|
+
|
50
|
+
if (notifications - NOTIFICATION_TYPES.keys).empty?
|
51
|
+
|
52
|
+
notifications.each { |n| receiver.add_observer(NOTIFICATION_TYPES[n].new) }
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.verify_type(type, types)
|
59
|
+
|
60
|
+
raise StandardError, "Unknown type - #{type}, possible options: #{types.keys}" unless types.keys.include? type
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
CHECKER_TYPES = {
|
65
|
+
|
66
|
+
:folder => Receiving::FolderChecker,
|
67
|
+
:imap => Receiving::IMAPChecker
|
68
|
+
|
69
|
+
}
|
70
|
+
|
71
|
+
SENDER_TYPES = {
|
72
|
+
|
73
|
+
:postmark_api => Sending::PostmarkAPISender,
|
74
|
+
:postmark_batch_api => Sending::PostmarkBatchAPISender,
|
75
|
+
:smtp => Sending::SMTPSender
|
76
|
+
|
77
|
+
}
|
78
|
+
|
79
|
+
NOTIFICATION_TYPES = {
|
80
|
+
|
81
|
+
:console => Receiving::Notification::Console,
|
82
|
+
:email => Receiving::Notification::Email
|
83
|
+
|
84
|
+
}
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative 'receiving/checker_folder'
|
2
|
+
require_relative 'receiving/checker_imap'
|
3
|
+
require_relative 'receiving/observer'
|
4
|
+
|
5
|
+
module MailHandler
|
6
|
+
|
7
|
+
class Receiver
|
8
|
+
|
9
|
+
include Receiving::Observer
|
10
|
+
|
11
|
+
attr_accessor :checker,
|
12
|
+
:search,
|
13
|
+
:search_max_duration
|
14
|
+
|
15
|
+
module DEFAULTS
|
16
|
+
|
17
|
+
MAX_SEARCH_DURATION = 240 # maximum time for search to last in [seconds]
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [Hash] - search options
|
22
|
+
# @param [Time] - search started at Time
|
23
|
+
# @param [Time] - search finished at Time
|
24
|
+
# @param [int] - how long search lasted
|
25
|
+
# @param [int] - how long search can last
|
26
|
+
# @param [boolean] - result of search
|
27
|
+
# @param [Mail] - first email found
|
28
|
+
# @param [Array] - all emails found
|
29
|
+
Search = Struct.new( :options, :started_at, :finished_at, :duration, :max_duration, :result, :email, :emails)
|
30
|
+
|
31
|
+
def initialize(checker)
|
32
|
+
|
33
|
+
@checker = checker
|
34
|
+
@search_max_duration = DEFAULTS::MAX_SEARCH_DURATION
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_email(options)
|
39
|
+
|
40
|
+
init_search_details(options)
|
41
|
+
|
42
|
+
while 1
|
43
|
+
|
44
|
+
received = checker.find(options) || search_time_expired?
|
45
|
+
update_search_details
|
46
|
+
notify_observers(search)
|
47
|
+
|
48
|
+
break if received
|
49
|
+
sleep 1
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
checker.search_result
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def init_search_details(options)
|
60
|
+
|
61
|
+
@search = Search.new
|
62
|
+
@search.options = options
|
63
|
+
@search.started_at = Time.now
|
64
|
+
@search.max_duration = @search_max_duration
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def update_search_details
|
69
|
+
|
70
|
+
search.finished_at = Time.now
|
71
|
+
search.duration = search.finished_at - search.started_at
|
72
|
+
search.result = checker.search_result
|
73
|
+
search.emails = checker.found_emails
|
74
|
+
search.email = search.emails.first
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
def search_time_expired?
|
79
|
+
|
80
|
+
(Time.now - search.started_at) > @search_max_duration
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module MailHandler
|
2
|
+
|
3
|
+
module Receiving
|
4
|
+
|
5
|
+
#
|
6
|
+
# Email receiving checker interface. All email checking types need to implement it.
|
7
|
+
# @see MailHandler::Receiving::FolderChecker for example for one of implemented checkers.
|
8
|
+
#
|
9
|
+
# Checker interface is used for doing a single check whether email is in your inbox.
|
10
|
+
#
|
11
|
+
class Checker
|
12
|
+
|
13
|
+
attr_accessor :search_options,
|
14
|
+
:found_emails
|
15
|
+
|
16
|
+
AVAILABLE_SEARCH_OPTIONS = [
|
17
|
+
|
18
|
+
:by_subject,
|
19
|
+
:by_content,
|
20
|
+
:by_date,
|
21
|
+
:by_recipient,
|
22
|
+
:count,
|
23
|
+
:archive
|
24
|
+
|
25
|
+
]
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
|
29
|
+
# Default number of email results to return, and whether to archive emails.
|
30
|
+
@search_options = {:count => 10, :archive => false}
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def find(options)
|
35
|
+
|
36
|
+
raise StandardError, 'Method not implemented'
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def verify_and_set_search_options(options)
|
41
|
+
|
42
|
+
unless (options.keys - AVAILABLE_SEARCH_OPTIONS).empty?
|
43
|
+
raise StandardError, "#{(options.keys - AVAILABLE_SEARCH_OPTIONS)} - Incorrect search option values, options are #{AVAILABLE_SEARCH_OPTIONS}"
|
44
|
+
end
|
45
|
+
|
46
|
+
@search_options = search_options.merge options
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
def search_result
|
51
|
+
|
52
|
+
found_emails != nil and !found_emails.empty?
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require_relative 'checker.rb'
|
2
|
+
require_relative 'filter'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'mail'
|
5
|
+
|
6
|
+
module MailHandler
|
7
|
+
|
8
|
+
module Receiving
|
9
|
+
|
10
|
+
class FolderChecker < Checker
|
11
|
+
|
12
|
+
# folders in which emails will be searched for and managed
|
13
|
+
attr_accessor :inbox_folder,
|
14
|
+
:archive_folder
|
15
|
+
|
16
|
+
def initialize(inbox_folder = nil, archive_folder = nil)
|
17
|
+
|
18
|
+
super()
|
19
|
+
|
20
|
+
@inbox_folder = inbox_folder
|
21
|
+
@archive_folder = archive_folder
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
# check whether email is received by checking for an email in folder
|
26
|
+
def find(options)
|
27
|
+
|
28
|
+
verify_and_set_search_options(options)
|
29
|
+
email_files = find_files(search_options)
|
30
|
+
|
31
|
+
if email_files.empty?
|
32
|
+
|
33
|
+
@found_emails = []
|
34
|
+
|
35
|
+
else
|
36
|
+
|
37
|
+
@found_emails = read_found_emails(email_files, search_options[:count])
|
38
|
+
move_files(email_files) if search_options[:archive]
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
!@found_emails.empty?
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# filter options which need to be done by searching files
|
49
|
+
FILE_SEARCH_OPTIONS = {
|
50
|
+
|
51
|
+
:by_subject => Filter::ByContent,
|
52
|
+
:by_content => Filter::ByContent,
|
53
|
+
:by_date => Filter::ByDate,
|
54
|
+
:by_recipient => Filter::Email::ByRecipient
|
55
|
+
}
|
56
|
+
|
57
|
+
def search_pattern
|
58
|
+
|
59
|
+
@inbox_folder + '/*.*'
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def read_found_emails(files, count)
|
64
|
+
|
65
|
+
files.first(count).map do |file|
|
66
|
+
|
67
|
+
email_content = File.read(file)
|
68
|
+
Mail.read_from_string(email_content)
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# find files by FILE_SEARCH_OPTIONS options
|
75
|
+
# this will ignore filter criteria options which can't be done on files directly
|
76
|
+
def find_files(options)
|
77
|
+
|
78
|
+
files = Filter::Base.new.get(search_pattern)
|
79
|
+
|
80
|
+
options.each do |key, value|
|
81
|
+
|
82
|
+
files = (files & FILE_SEARCH_OPTIONS[key].new(value).get(search_pattern)) if FILE_SEARCH_OPTIONS[key] != nil
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
Filter::Base.sort(files)
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
def move_files(files)
|
91
|
+
|
92
|
+
setup_inbox_folders
|
93
|
+
|
94
|
+
files.each do |file|
|
95
|
+
|
96
|
+
file = File.basename(file)
|
97
|
+
|
98
|
+
if inbox_folder != archive_folder
|
99
|
+
|
100
|
+
FileUtils.mv("#{inbox_folder}/#{file}", "#{archive_folder}/#{file}")
|
101
|
+
|
102
|
+
else
|
103
|
+
|
104
|
+
FileUtils.rm_r "#{inbox_folder}/#{file}", :force => true
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
# create folders if they don't exist
|
113
|
+
def setup_inbox_folders
|
114
|
+
|
115
|
+
raise StandardError, 'Folder variables are not set' if inbox_folder.nil? or archive_folder.nil?
|
116
|
+
|
117
|
+
Dir::mkdir inbox_folder unless File.directory? inbox_folder
|
118
|
+
Dir::mkdir archive_folder unless File.directory? archive_folder
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'mail'
|
2
|
+
require_relative 'checker.rb'
|
3
|
+
# encoding: utf-8
|
4
|
+
|
5
|
+
module MailHandler
|
6
|
+
|
7
|
+
module Receiving
|
8
|
+
|
9
|
+
class IMAPChecker < Checker
|
10
|
+
|
11
|
+
AVAILABLE_SEARCH_OPTIONS = [
|
12
|
+
|
13
|
+
:by_subject,
|
14
|
+
:count,
|
15
|
+
:archive
|
16
|
+
|
17
|
+
]
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
|
21
|
+
super
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
# set email account from which we will read email
|
26
|
+
def imap_details(address, port, username, password, use_ssl)
|
27
|
+
|
28
|
+
Mail.defaults do
|
29
|
+
retriever_method :imap,
|
30
|
+
:address => address,
|
31
|
+
:port => port,
|
32
|
+
:user_name => username,
|
33
|
+
:password => password,
|
34
|
+
:enable_ssl => use_ssl
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
def find(options)
|
40
|
+
|
41
|
+
verify_and_set_search_options(options)
|
42
|
+
validate_options(options)
|
43
|
+
emails = find_emails(search_options)
|
44
|
+
|
45
|
+
@found_emails = []
|
46
|
+
|
47
|
+
unless emails.empty?
|
48
|
+
|
49
|
+
emails.each do |email|
|
50
|
+
|
51
|
+
found = email.subject.include? search_options[:by_subject]
|
52
|
+
@found_emails << email if found
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
!@found_emails.empty?
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def find_emails(options)
|
65
|
+
|
66
|
+
if options[:archive]
|
67
|
+
|
68
|
+
Mail.find_and_delete(:what => :last, :count => search_options[:count], :order => :desc, :keys => ['SUBJECT', search_options[:by_subject]])
|
69
|
+
|
70
|
+
else
|
71
|
+
|
72
|
+
Mail.find(:what => :last, :count => search_options[:count], :order => :desc, :keys => ['SUBJECT', search_options[:by_subject]])
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
def validate_options(options)
|
79
|
+
|
80
|
+
unless (options.keys - AVAILABLE_SEARCH_OPTIONS).empty?
|
81
|
+
raise StandardError, "#{(options.keys - AVAILABLE_SEARCH_OPTIONS)} - Not supported search option values for imap, options are #{AVAILABLE_SEARCH_OPTIONS}"
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|