Class: AutoHeathen::EmailProcessor

Inherits:
Object
  • Object
show all
Includes:
Config
Defined in:
lib/autoheathen/email_processor.rb

Constant Summary collapse

ONWARD_HEADERS =

The only valid email headers we will allow forward to LEG_wikilex

['Date','From','To','Subject','Content-Type','Content-Transfer-Encoding','Mime-Version']

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Config

#load_config, #symbolize_keys

Constructor Details

#initialize(cfg = {}, config_file = nil) ⇒ EmailProcessor

Constructs the processor

Parameters:

  • cfg (defaults to: {})

    a hash of configuration settings: deliver: true If false, email will not be actually sent (useful for testing) email: nil Email to send response to (if mode == :email) from: 'autoheathen' Who to say the email is from cc_blacklist: nil Array of email addresses to excise from CC list of any mails

    - used to avoid infinite loops in autoheathen
    

    mail_host: 'localhost' Mail relay host for responses (mode in [:return_to_sender,:email] mail_port: 25 Mail relay port (ditto) text_template: 'config/response.text.haml' Template for text part of response email (mode in [:return_to_sender,:email]) html_template: 'config/response.html.haml' Template for HTML part of response email (ditto) logger: nil Optional logger object


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/autoheathen/email_processor.rb', line 30

def initialize cfg={}, config_file=nil
  @cfg = load_config( {   # defaults
      deliver:          true,
      language:         'en',
      from:             'autoheathen',
      cc_blacklist:     nil,
      email:            nil,
      verbose:          false,
      mail_host:        'localhost',
      mail_port:        25,
      logger:           nil,
      text_template:    'config/autoheathen.text.haml',
      html_template:    'config/autoheathen.html.haml',
    }, config_file, cfg )
  @logger = @cfg[:logger] || Logger.new(nil)
  @logger.level = @cfg[:verbose] ? Logger::DEBUG : Logger::INFO
end

Instance Attribute Details

#cfgObject (readonly)

Returns the value of attribute cfg


16
17
18
# File 'lib/autoheathen/email_processor.rb', line 16

def cfg
  @cfg
end

#loggerObject (readonly)

Returns the value of attribute logger


16
17
18
# File 'lib/autoheathen/email_processor.rb', line 16

def logger
  @logger
end

Instance Method Details

#deliver(mail) ⇒ Object

Convenience method allowing us to stub out actual mail delivery in RSpec


174
175
176
177
178
179
180
181
# File 'lib/autoheathen/email_processor.rb', line 174

def deliver mail
  if @cfg[:deliver]
    mail.deliver!
    logger.debug "Files were emailed to #{mail.to}"
  else
    logger.debug "Files would have been emailed to #{mail.to}, but #{self.class.name} is configured not to"
  end
end

#deliver_onward(email, documents, mail_to) ⇒ Object

Forward the email to sender, with decoded documents replacing the originals


108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/autoheathen/email_processor.rb', line 108

def deliver_onward email, documents, mail_to
  logger.info "Sending response mail to #{mail_to}"
  email.cc [] # No CCing, just send to the recipient
  email.to mail_to
  email.subject "#{'Fwd: ' unless email.subject.to_s.start_with? 'Fwd:'}#{email.subject}"
  email.return_path email.from unless email.return_path  # something weird goes on with Sharepoint, where the doc is dropped on the floor
  # so, remove any offending headers

  email.message_id = nil # make sure of message_id too
  good_headers = ONWARD_HEADERS.map{ |h| h.downcase }
  inspect_headers = email.header.map(&:name)
  inspect_headers .each do |name|
    unless good_headers.include? name.downcase
      email.header[name] = nil
    end
  end
  email.received = nil # make sure of received
  # replace attachments with converted files
  email.parts.delete_if { |p| p.attachment? }
  documents.each do |doc|
    if doc[:content]
      email.add_file filename: doc[:filename], content: doc[:content]
    else # preserve non-converted attachments when forwarding
      email.add_file filename: doc[:orig_filename], content: doc[:orig_content]
    end
  end
  email.delivery_method :smtp, address: @cfg[:mail_host], port: @cfg[:mail_port]
  deliver email
end

#deliver_rts(email, documents, mail_to) ⇒ Object

Send decoded documents back to sender


139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/autoheathen/email_processor.rb', line 139

def deliver_rts email, documents, mail_to
  logger.info "Sending response mail to #{mail_to}"
  mail = Mail.new
  mail.from @cfg[:from]
  mail.to mail_to  # CCs to the original email will get a copy of the converted files as well

  mail.cc (email.cc - email.to - (@cfg[:cc_blacklist]||[]) ) if email.cc # Prevent autoheathen infinite loop!
  # Don't prepend yet another Re:
  mail.subject "#{'Re: ' unless email.subject.start_with? 'Re:'}#{email.subject}"  # Construct received path
  # TODO: is this in the right order?
  #rcv = "by localhost(autoheathen); #{Time.now.strftime '%a, %d %b %Y %T %z'}"
  #[email.received,rcv].flatten.each { |rec| mail.received rec.to_s }

  mail.return_path email.return_path if email.return_path
  mail.header['X-Received'] = email.header['X-Received'] if email.header['X-Received']
  documents.each do |doc|
    next if doc[:content].nil?
    mail.add_file filename: doc[:filename], content: doc[:content]
  end
  cfg = @cfg # stoopid Mail scoping
  me = self # stoopid Mail scoping
  mail.text_part do
    s = Haml::Engine.new( me.read_file cfg[:text_template] ).render(Object.new, to: mail_to, documents: documents, cfg: cfg)
    body s
  end
  mail.html_part do
    content_type 'text/html; charset=UTF-8'
    s = Haml::Engine.new( me.read_file cfg[:html_template] ).render(Object.new, to: mail_to, documents: documents, cfg: cfg)
    body s
  end
  mail.delivery_method :smtp, address: @cfg[:mail_host], port: @cfg[:mail_port]
  deliver mail
end

#get_action(content_type) ⇒ Object

Returns the correct conversion action based on the content type

Raises:

  • RuntimeError if there is no conversion action for the content type


194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/autoheathen/email_processor.rb', line 194

def get_action content_type
  ct = content_type.gsub(/;.*/, '')
  op = {
    'application/pdf' => 'ocr',
    'text/html' => 'pdf',
    'application/zip' => 'pdf',
    'application/msword' => 'pdf',
    'application/vnd.oasis.opendocument.text' => 'pdf',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'pdf',
    'application/vnd.ms-excel' => 'pdf',
    'application/vnd.ms-office' => 'pdf',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'pdf',
    'application/vnd.ms-powerpoint' => 'pdf',
    'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pdf',
  }[ct]
  op = 'ocr' if ! op && ct.start_with?('image/')
  raise "Conversion from #{ct} is not supported" unless op
  op
end

#process(email, mail_to, is_rts = false) ⇒ Hash

Processes the given email, submits attachments to the Heathen server, delivers responses as configured

Parameters:

  • email (String)

    The encoded email (suitable to be decoded using Mail.read(input))

Returns:

  • (Hash)

    a hash of the decoded attachments (or the reason why they could not be decoded)


55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/autoheathen/email_processor.rb', line 55

def process email, mail_to, is_rts=false
  documents = []

  unless email.has_attachments?
    logger.info "From: #{email.from} Subject: (#{email.subject}) Files: no attachments"
    return
  end

  logger.info "From: #{email.from} Subject: (#{email.subject}) Files: #{email.attachments.map(&:filename).join(',')}"

  #
  # Convert the attachments
  #
  email.attachments.each do |attachment|
    begin
      converter = Heathen::Converter.new( logger: logger )
      language = @cfg[:language]
      input_source = attachment.body.decoded
      action = get_action input_source.content_type
      logger.info "    convert #{attachment.filename} using action: #{action}"
      data = converter.convert action, input_source, language
      converted_filename = Heathen::Filename.suggest attachment.filename, data.mime_type
      documents << { orig_filename: attachment.filename, orig_content: input_source, filename: converted_filename, content: data, error: false }
    rescue StandardError => e
      documents << { orig_filename: attachment.filename, orig_content: input_source, filename: nil, content: nil, error: e.message }
    end
  end

  #
  # deliver the results
  #
  if is_rts
    deliver_rts email, documents, mail_to
  else
    deliver_onward email, documents, mail_to
  end

  #
  # Summarise the processing
  #
  logger.info "Results of conversion"
  documents.each do |doc|
    if doc[:content].nil?
      logger.info "  #{doc[:orig_filename]} was not converted (#{doc[:error]}) "
    else
      logger.info "  #{doc[:orig_filename]} was converted successfully"
    end
  end

  documents
end

#process_rts(email) ⇒ Object


48
49
50
# File 'lib/autoheathen/email_processor.rb', line 48

def process_rts email
  process email, email.from, true
end

#read_file(filename) ⇒ Object

Opens and reads a file, first given the filename, then tries from the project base directory


184
185
186
187
188
189
190
# File 'lib/autoheathen/email_processor.rb', line 184

def read_file filename
  f = filename
  unless File.exist? f
    f = Pathname.new(__FILE__).realpath.parent.parent.parent + f
  end
  File.read f
end