aiwilliams-mlist 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +23 -0
- data/Rakefile +1 -1
- data/VERSION.yml +1 -1
- data/lib/mlist.rb +20 -1
- data/lib/mlist/email.rb +75 -0
- data/lib/mlist/email_post.rb +105 -0
- data/lib/mlist/email_server.rb +1 -2
- data/lib/mlist/email_server/base.rb +7 -4
- data/lib/mlist/email_server/default.rb +31 -0
- data/lib/mlist/email_server/fake.rb +2 -2
- data/lib/mlist/email_server/pop.rb +28 -0
- data/lib/mlist/email_server/smtp.rb +21 -0
- data/lib/mlist/email_subscriber.rb +6 -0
- data/lib/mlist/list.rb +38 -7
- data/lib/mlist/mail_list.rb +125 -54
- data/lib/mlist/manager/database.rb +8 -4
- data/lib/mlist/message.rb +78 -74
- data/lib/mlist/server.rb +12 -4
- data/lib/mlist/thread.rb +26 -2
- data/lib/mlist/util.rb +3 -0
- data/lib/mlist/util/email_helpers.rb +53 -0
- data/lib/mlist/util/header_sanitizer.rb +3 -0
- data/lib/mlist/util/tmail_builder.rb +42 -0
- data/lib/mlist/util/tmail_methods.rb +93 -0
- data/lib/pop_ssl.rb +999 -0
- metadata +12 -3
- data/lib/mlist/email_server/email.rb +0 -47
data/lib/mlist/mail_list.rb
CHANGED
@@ -1,77 +1,148 @@
|
|
1
1
|
module MList
|
2
2
|
class MailList < ActiveRecord::Base
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
set_table_name 'mlist_mail_lists'
|
4
|
+
|
5
|
+
# Provides the MailList for a given implementation of MList::List,
|
6
|
+
# connecting it to the provided email server for delivering posts.
|
7
|
+
#
|
8
|
+
def self.find_or_create_by_list(list, outgoing_server)
|
9
|
+
if list.is_a?(ActiveRecord::Base)
|
10
|
+
mail_list = find_or_create_by_manager_list_identifier_and_manager_list_type_and_manager_list_id(
|
11
|
+
list.list_id, list.class.base_class.name, list.id
|
12
|
+
)
|
13
|
+
else
|
14
|
+
mail_list = find_or_create_by_manager_list_identifier(list.list_id)
|
15
|
+
mail_list.manager_list = list
|
16
|
+
end
|
17
|
+
mail_list.outgoing_server = outgoing_server
|
6
18
|
mail_list
|
7
19
|
end
|
8
20
|
|
9
|
-
|
10
|
-
has_many :threads, :dependent => :delete_all
|
21
|
+
belongs_to :manager_list, :polymorphic => true
|
11
22
|
|
12
|
-
|
13
|
-
|
14
|
-
:to => :manager_list
|
23
|
+
has_many :messages, :class_name => 'MList::Message', :dependent => :delete_all
|
24
|
+
has_many :threads, :class_name => 'MList::Thread', :dependent => :delete_all
|
15
25
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
deliver(message, email_server)
|
20
|
-
end
|
26
|
+
delegate :address, :label, :post_url, :to => :list
|
27
|
+
|
28
|
+
attr_accessor :outgoing_server
|
21
29
|
|
22
|
-
|
23
|
-
|
30
|
+
# Creates a new MList::Message and delivers it to the subscribers of this
|
31
|
+
# list.
|
32
|
+
#
|
33
|
+
def post(email_or_attributes)
|
34
|
+
email = email_or_attributes
|
35
|
+
email = MList::EmailPost.new(email_or_attributes) unless email.is_a?(MList::EmailPost)
|
36
|
+
process_message messages.build(
|
37
|
+
:parent => email.reply_to_message,
|
38
|
+
:parent_identifier => email.parent_identifier,
|
39
|
+
:mail_list => self,
|
40
|
+
:subscriber => email.subscriber,
|
41
|
+
:recipients => list.recipients(email.subscriber),
|
42
|
+
:email => MList::Email.new(:tmail => email.to_tmail)
|
43
|
+
), :search_parent => false
|
24
44
|
end
|
25
45
|
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
46
|
+
# Processes the email received by the MList::Server.
|
47
|
+
#
|
48
|
+
def process_email(email)
|
49
|
+
subscriber = list.subscriber(email.from_address)
|
50
|
+
recipients = list.recipients(subscriber)
|
51
|
+
process_message messages.build(
|
52
|
+
:mail_list => self,
|
53
|
+
:subscriber => subscriber,
|
54
|
+
:recipients => recipients,
|
55
|
+
:email => email
|
56
|
+
)
|
30
57
|
end
|
31
58
|
|
32
|
-
def
|
33
|
-
|
34
|
-
email_server.deliver(message.tmail)
|
35
|
-
thread = find_thread(message)
|
36
|
-
thread.messages << message
|
37
|
-
thread.save!
|
38
|
-
end
|
59
|
+
def list
|
60
|
+
@list ||= manager_list
|
39
61
|
end
|
40
62
|
|
41
|
-
def
|
42
|
-
if
|
43
|
-
|
44
|
-
|
45
|
-
:readonly => false,
|
46
|
-
:conditions => ['messages.identifier = ?', message.parent_identifier]
|
47
|
-
)
|
63
|
+
def manager_list_with_dual_type=(list)
|
64
|
+
if list.is_a?(ActiveRecord::Base)
|
65
|
+
self.manager_list_without_dual_type = list
|
66
|
+
@list = list
|
48
67
|
else
|
49
|
-
|
68
|
+
self.manager_list_without_dual_type = nil
|
69
|
+
@list = list
|
50
70
|
end
|
51
71
|
end
|
72
|
+
alias_method_chain :manager_list=, :dual_type
|
52
73
|
|
53
|
-
|
54
|
-
|
55
|
-
headers = manager_list.list_headers
|
56
|
-
headers['x-beenthere'] = address
|
57
|
-
headers.update(bounce_headers)
|
58
|
-
headers.delete_if {|k,v| v.nil?}
|
59
|
-
end
|
60
|
-
|
61
|
-
def prepare_delivery(message)
|
62
|
-
prepare_list_headers(message)
|
63
|
-
message.to = address
|
64
|
-
message.bcc = recipients(message)
|
74
|
+
def process?(message)
|
75
|
+
!message.recipients.blank?
|
65
76
|
end
|
66
77
|
|
67
|
-
|
68
|
-
|
69
|
-
|
78
|
+
private
|
79
|
+
# http://mail.python.org/pipermail/mailman-developers/2006-April/018718.html
|
80
|
+
def bounce_headers
|
81
|
+
{'sender' => "mlist-#{address}",
|
82
|
+
'errors-to' => "mlist-#{address}"}
|
83
|
+
end
|
84
|
+
|
85
|
+
# http://www.jamesshuggins.com/h/web1/list-email-headers.htm
|
86
|
+
def list_headers
|
87
|
+
headers = list.list_headers
|
88
|
+
headers['x-beenthere'] = address
|
89
|
+
headers.update(bounce_headers)
|
90
|
+
headers.delete_if {|k,v| v.nil?}
|
91
|
+
end
|
92
|
+
|
93
|
+
def process_message(message, options = {})
|
94
|
+
raise MList::DoubleDeliveryError.new(message) unless message.new_record?
|
95
|
+
return message unless process?(message)
|
96
|
+
|
97
|
+
options = {
|
98
|
+
:search_parent => true,
|
99
|
+
:delivery_time => Time.now
|
100
|
+
}.merge(options)
|
101
|
+
transaction do
|
102
|
+
thread = find_thread(message, options)
|
103
|
+
thread.messages << message
|
104
|
+
prepare_delivery(message)
|
105
|
+
destinations = message.delivery.destinations
|
106
|
+
tmail = message.to_tmail
|
107
|
+
self.updated_at = thread.updated_at = options[:delivery_time]
|
108
|
+
thread.save! && save!
|
109
|
+
outgoing_server.deliver(tmail, destinations)
|
110
|
+
end
|
111
|
+
message
|
112
|
+
end
|
113
|
+
|
114
|
+
def prepare_delivery(message)
|
115
|
+
delivery = message.delivery
|
116
|
+
prepare_list_headers(delivery)
|
117
|
+
delivery.subject = list_subject(message)
|
118
|
+
delivery.to = address
|
119
|
+
delivery.bcc = message.recipients
|
120
|
+
delivery.reply_to = "#{label} <#{post_url}>"
|
121
|
+
end
|
122
|
+
|
123
|
+
def prepare_list_headers(delivery)
|
124
|
+
list_headers.each do |k,v|
|
125
|
+
if TMail::Mail::ALLOW_MULTIPLE.include?(k.downcase)
|
126
|
+
delivery.prepend_header(k,v)
|
127
|
+
else
|
128
|
+
delivery.write_header(k,v)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def list_subject(message)
|
134
|
+
prefix = "[#{label}]"
|
135
|
+
subject = message.subject.gsub(%r(#{Regexp.escape(prefix)}\s*), '')
|
136
|
+
subject.gsub!(%r{(re:\s*){2,}}i, 'Re: ')
|
137
|
+
"#{prefix} #{subject}"
|
138
|
+
end
|
139
|
+
|
140
|
+
def find_thread(message, options)
|
141
|
+
if options[:search_parent]
|
142
|
+
message.parent_identifier = message.email.parent_identifier(self)
|
143
|
+
message.parent = messages.find_by_identifier(message.parent_identifier)
|
144
|
+
end
|
145
|
+
message.parent ? message.parent.thread : threads.build
|
70
146
|
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def process?(message)
|
74
|
-
!been_there?(message) && !recipients(message).blank?
|
75
|
-
end
|
76
147
|
end
|
77
148
|
end
|
@@ -12,20 +12,24 @@ module MList
|
|
12
12
|
|
13
13
|
def lists(email)
|
14
14
|
lists = List.find_all_by_address(email.list_addresses)
|
15
|
-
email.list_addresses.map { |a| lists.detect {|l| l.address == a} }
|
15
|
+
email.list_addresses.map { |a| lists.detect {|l| l.address == a} }.compact
|
16
16
|
end
|
17
17
|
|
18
18
|
class List < ActiveRecord::Base
|
19
19
|
include ::MList::List
|
20
20
|
|
21
|
-
has_many :
|
21
|
+
has_many :subscribers, :dependent => :delete_all
|
22
|
+
|
23
|
+
def list_id
|
24
|
+
"#{self.class.name}#{id}"
|
25
|
+
end
|
22
26
|
|
23
27
|
def subscribe(address)
|
24
|
-
|
28
|
+
subscribers.find_or_create_by_email_address(address)
|
25
29
|
end
|
26
30
|
end
|
27
31
|
|
28
|
-
class
|
32
|
+
class Subscriber < ActiveRecord::Base
|
29
33
|
belongs_to :list
|
30
34
|
end
|
31
35
|
end
|
data/lib/mlist/message.rb
CHANGED
@@ -1,107 +1,111 @@
|
|
1
1
|
module MList
|
2
2
|
|
3
|
-
# The persisted version of an email that is processed by MList::MailLists.
|
4
|
-
#
|
5
|
-
# The tmail object referenced by these are unique, though they may reference
|
6
|
-
# the 'same' originating email.
|
7
|
-
#
|
8
3
|
class Message < ActiveRecord::Base
|
9
|
-
|
4
|
+
set_table_name 'mlist_messages'
|
10
5
|
|
11
|
-
|
12
|
-
before_save :serialize_tmail
|
6
|
+
include MList::Util::EmailHelpers
|
13
7
|
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
belongs_to :email, :class_name => 'MList::Email'
|
9
|
+
belongs_to :parent, :class_name => 'MList::Message'
|
10
|
+
belongs_to :mail_list, :class_name => 'MList::MailList', :counter_cache => :messages_count
|
11
|
+
belongs_to :thread, :class_name => 'MList::Thread', :counter_cache => :messages_count
|
17
12
|
|
18
|
-
|
19
|
-
|
20
|
-
|
13
|
+
# A temporary storage of recipient subscribers, obtained from
|
14
|
+
# MList::Lists. This list is not available when a message is reloaded.
|
15
|
+
#
|
16
|
+
attr_accessor :recipients
|
21
17
|
|
22
|
-
def
|
23
|
-
|
18
|
+
def delivery
|
19
|
+
@delivery ||= MList::Util::TMailBuilder.new(TMail::Mail.parse(email.source))
|
24
20
|
end
|
25
21
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
identifier = references.ids.first
|
31
|
-
else
|
32
|
-
parent_message = mail_list.messages.find(:first,
|
33
|
-
:conditions => ['messages.subject = ?', remove_regard(subject)],
|
34
|
-
:order => 'created_at asc'
|
35
|
-
)
|
36
|
-
identifier = parent_message.identifier if parent_message
|
37
|
-
end
|
38
|
-
remove_brackets(identifier) if identifier
|
22
|
+
def email_with_capture=(email)
|
23
|
+
self.subject = email.subject
|
24
|
+
self.mailer = email.mailer
|
25
|
+
self.email_without_capture = email
|
39
26
|
end
|
27
|
+
alias_method_chain :email=, :capture
|
40
28
|
|
41
|
-
|
42
|
-
|
29
|
+
# Answers the html content of the message.
|
30
|
+
#
|
31
|
+
def html
|
32
|
+
email.html
|
43
33
|
end
|
44
34
|
|
45
|
-
|
46
|
-
|
35
|
+
# Answers the text content of the message.
|
36
|
+
#
|
37
|
+
def text
|
38
|
+
email.text
|
47
39
|
end
|
48
40
|
|
49
|
-
|
50
|
-
|
41
|
+
# Answers the text content of the message as HTML. The structure of this
|
42
|
+
# output is very simple. For examples of what it can handle, please check
|
43
|
+
# out the spec documents for MList::Util::EmailHelpers.
|
44
|
+
#
|
45
|
+
def text_html
|
46
|
+
text_to_html(text)
|
51
47
|
end
|
52
48
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
49
|
+
# Answers text suitable for creating a reply message.
|
50
|
+
#
|
51
|
+
def text_for_reply
|
52
|
+
timestamp = email.date.to_s(:mlist_reply_timestamp)
|
53
|
+
"On #{timestamp}, #{email.from} wrote:\n#{text_to_quoted(text)}"
|
57
54
|
end
|
58
55
|
|
59
|
-
|
60
|
-
|
56
|
+
# Answers text suitable for creating a reply message, converted to the
|
57
|
+
# same simple html of _text_html_.
|
58
|
+
#
|
59
|
+
def html_for_reply
|
60
|
+
text_to_html(text_for_reply)
|
61
61
|
end
|
62
62
|
|
63
|
-
|
64
|
-
|
63
|
+
# Answers the subject with all prefixes removed.
|
64
|
+
#
|
65
|
+
# message.subject = 'Re: [List Label] Re: The new Chrome Browser from Google'
|
66
|
+
# message.subject_for_reply => 'Re: The new Chrome Browser from Google'
|
67
|
+
#
|
68
|
+
def subject_for_reply
|
69
|
+
"Re: #{remove_regard(subject)}"
|
65
70
|
end
|
66
71
|
|
67
|
-
|
68
|
-
|
72
|
+
# Answers the subscriber from which this message comes.
|
73
|
+
#
|
74
|
+
def subscriber
|
75
|
+
@subscriber ||= begin
|
76
|
+
if subscriber_type? && subscriber_id?
|
77
|
+
subscriber_type.constantize.find(subscriber_id)
|
78
|
+
elsif subscriber_address?
|
79
|
+
MList::EmailSubscriber.new(subscriber_address)
|
80
|
+
end
|
81
|
+
end
|
69
82
|
end
|
70
83
|
|
71
|
-
#
|
72
|
-
# excluding those overridden by this class and the [] and []= methods. We
|
73
|
-
# must maintain the ActiveRecord interface over that of the TMail::Mail
|
74
|
-
# interface.
|
84
|
+
# Assigns the subscriber from which this message comes.
|
75
85
|
#
|
76
|
-
def
|
77
|
-
|
78
|
-
|
86
|
+
def subscriber=(subscriber)
|
87
|
+
case subscriber
|
88
|
+
when ActiveRecord::Base
|
89
|
+
@subscriber = subscriber
|
90
|
+
self.subscriber_address = subscriber.email_address
|
91
|
+
self.subscriber_type = subscriber.class.base_class.name
|
92
|
+
self.subscriber_id = subscriber.id
|
93
|
+
when MList::EmailSubscriber
|
94
|
+
@subscriber = subscriber
|
95
|
+
self.subscriber_address = subscriber.email_address
|
96
|
+
self.subscriber_type = self.subscriber_id = nil
|
97
|
+
when String
|
98
|
+
self.subscriber = MList::EmailSubscriber.new(subscriber)
|
79
99
|
else
|
80
|
-
|
100
|
+
@subscriber = self.subscriber_address = self.subscriber_type = self.subscriber_id = nil
|
81
101
|
end
|
82
102
|
end
|
83
103
|
|
84
|
-
def
|
85
|
-
|
104
|
+
def to_tmail
|
105
|
+
delivery.mailer = mailer
|
106
|
+
delivery.ready_to_send
|
107
|
+
self.identifier = delivery.identifier
|
108
|
+
delivery.tmail
|
86
109
|
end
|
87
|
-
|
88
|
-
private
|
89
|
-
def header_sanitizer(name)
|
90
|
-
@header_sanitizers ||= Util.default_header_sanitizers
|
91
|
-
@header_sanitizers[name]
|
92
|
-
end
|
93
|
-
|
94
|
-
def remove_brackets(string)
|
95
|
-
string =~ /\A<(.*?)>\Z/ ? $1 : string
|
96
|
-
end
|
97
|
-
|
98
|
-
def remove_regard(string)
|
99
|
-
stripped = string.strip
|
100
|
-
stripped =~ /\Are:\s+(.*?)\Z/i ? $1 : stripped
|
101
|
-
end
|
102
|
-
|
103
|
-
def serialize_tmail
|
104
|
-
write_attribute(:email_text, @tmail.to_s)
|
105
|
-
end
|
106
110
|
end
|
107
111
|
end
|
data/lib/mlist/server.rb
CHANGED
@@ -8,7 +8,7 @@ module MList
|
|
8
8
|
@email_server.receiver(self)
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
11
|
+
def receive_email(email)
|
12
12
|
lists = list_manager.lists(email)
|
13
13
|
if email.bounce?
|
14
14
|
process_bounce(lists.first, email)
|
@@ -17,6 +17,10 @@ module MList
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
def mail_list(list)
|
21
|
+
MailList.find_or_create_by_list(list, @email_server)
|
22
|
+
end
|
23
|
+
|
20
24
|
protected
|
21
25
|
def process_bounce(list, email)
|
22
26
|
list.bounce(email)
|
@@ -24,11 +28,15 @@ module MList
|
|
24
28
|
|
25
29
|
def process_post(lists, email)
|
26
30
|
lists.each do |list|
|
31
|
+
next if email.been_here?(list)
|
27
32
|
if list.subscriber?(email.from_address)
|
28
|
-
|
29
|
-
|
33
|
+
if list.active?
|
34
|
+
mail_list(list).process_email(email)
|
35
|
+
else
|
36
|
+
list.inactive_post(email)
|
37
|
+
end
|
30
38
|
else
|
31
|
-
list.
|
39
|
+
list.non_subscriber_post(email)
|
32
40
|
end
|
33
41
|
end
|
34
42
|
end
|