aiwilliams-mlist 0.0.0 → 0.1.0
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.
- 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
|