autoreply/reply.py

100 lines
3.1 KiB
Python
Executable File

#!/usr/bin/env python3
from email import message, message_from_binary_file, policy
from email.generator import BytesGenerator
from inspect import getsourcefile
from os.path import abspath, dirname, exists, join, realpath
import re
from subprocess import Popen, PIPE
from sys import argv, stdin
# Hack to determine the directory this script resides in,
# regardless of symlinks or other weirdness.
basedir = dirname(realpath(abspath(getsourcefile(lambda:0))))
# Set a sane email policy.
mypolicy = policy.default
# Read the incoming message.
inmsg = message_from_binary_file(stdin.buffer, policy=mypolicy)
# Bail if the incoming message was automatically generated
auto = inmsg['Auto-Submitted']
if auto and auto.lower() != 'no':
exit()
# Build the outgoing reply.
msg = message.EmailMessage(policy=mypolicy)
## Basic headers.
msg['To'] = inmsg['From']
msg['From'] = inmsg['To']
## Only add "re" to the subject if it's not already there.
if inmsg['Subject'].startswith(('re: ', 'Re: ', 'Re ', 're ')):
msg['Subject'] = inmsg['Subject']
else:
msg['Subject'] = 'Re: ' + inmsg['Subject']
## Set up correct threading identifiers.
if inmsg['Message-ID']:
msg['In-Reply-To'] = inmsg['Message-ID']
if inmsg['References']:
msg['References'] = inmsg['References'] +' '+ inmsg.get('Message-ID', '')
elif inmsg['In-Reply-To'] or inmsg['Message-ID']:
msg['References'] = inmsg.get('In-Reply-To', '') +' '+ inmsg.get('Message-ID', '')
## Add loop avoidance header (RFC3834)
msg['Auto-Submitted'] = 'auto-generated'
## Also add the exchange-specific anti-loop header
## https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcmail/ced68690-498a-4567-9d14-5c01f974d8b1
msg['X-Auto-Response-Suppress'] = 'All'
## Set the template directory.
templatedir = join(basedir, 'templates')
## Pick an autoreply template.
### Start with a sane default.
mytemplate = 'default'
### Try a template based on the recipient address.
#### Extract the address part from the To: header.
#### The regex handles: "name" <address@domain>
#### <address@domain>
#### address@domain
matches = re.match(r'("[^"]+" +)?<?([^>]+)>?', inmsg['To'])
address = matches.group(2)
#### If there was a match, use the name part to look for a template and use
#### it if it exists.
if address:
trytemplate = address.split('@')[0]
if exists(join(templatedir, trytemplate)):
mytemplate = trytemplate
### If a template was specified on the commandline, use that instead.
### Will cause an error if the template doesn't exist. This is intentional.
if len(argv) > 1:
mytemplate = argv[1]
## Set response body.
with open(join(templatedir, mytemplate)) as f:
msg.set_content(f.read())
## Include the original email
msg.add_related(inmsg)
## Include html version if one exists
htmlpath = join(templatedir, mytemplate + '.html')
if exists(htmlpath):
with open(htmlpath) as f:
msg.add_alternative(f.read(), subtype='html')
# Post the reply
p = Popen(['/usr/sbin/sendmail', '-t'], stdin=PIPE)
g = BytesGenerator(p.stdin, policy=msg.policy.clone(linesep='\r\n'))
g.flatten(msg)
p.stdin.close()
p.wait()