Почтовый робот для конвертации DOCX/XLSX в DOC/XLS

Компания Microsoft очень любит изменять форматы документов по-умолчанию в своём офисном пакете. В нашей компании в силу разных причин переходить на версию офиса 2007/2010 не стали. Но что делать, если поток входящих документов в новых форматах растёт с каждым днём? Можно установить средство от Microsoft, можно вообще перейти на LibreOffice, в котором есть поддержка новых форматов. Но ни тот, ни другой вариант нас полностью не устроил. Тогда и возникла идея сделать почтового робота.

Постановка задачи

Порядок работы с почтовым роботом должен быть предельно прост: пользователь отправляет письмо на некоторый адрес с вложенными документами, а в ответ получает письмо с конвертированными файлами.

Инструменты и приборы в наличии

В качестве почтового сервера у нас используется старый добрый Sendmail на RHEL5. Одной из его интересных фич является возможнось создания алиасов — виртуальных почтовых адресов, входящая почта на которые может быть обработана скриптом. Согласно настройкам безопасности, такие скрипты будут запущены внутри особого шелла smrsh, про настройки которого можно почитать в Интернете.

Для конвертации файлов можно использовать режим коммандной строки LibreOffice.

Алгоритм

Остаётся написать скрипт на любом языке, который будет делать следующее:

  1. получит письмо от sendmail
  2. выделит из него вложенные документы в формате MS Office 2007/2010
  3. запустит soffice с нужными параметрами
  4. подберёт получившиеся файлы
  5. и отправит их назад

Код

Я использовал python в силу того, что в RHEL5 он уже стоял (версии 2.4) вместе со всеми необходимыми библиотеками.

conv.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import sys, os, tempfile, string
# библиотека для работы с SMTP
import smtplib
# библиотеки для разбора email
import email.Message
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase
from email.Header import make_header
from email.Utils import formatdate
 
# адрес и порт SMTP-сервера для отправки
server = '192.168.1.1'
port = 25
# кодировка обратного письма
icharset = 'koi8-r'
# кодировка для имен файлов
encodeto = 'koi8_r'
# путь к LibreOffice
soffice = "/opt/libreoffice3.5/program/soffice"
# форматы и фильтры для конвертации
# ограничимся вордом и экселем
convfilters = {'doc':'doc:"MS Word 97"','xls':'xls:"MS Excel 97"'}
 
# функция для отправки сообщения
def sendmail(to, subject, message, files):
        from_address = 'conv'
        msg = MIMEMultipart()
        hdr = make_header([(subject, icharset)])
        msg['From'] = from_address
        msg['To'] = to
        msg['Date'] = formatdate(localtime=True)
        msg['Subject'] = hdr
 
        msg.attach(MIMEText(message, _charset=icharset))
 
        for filename in files:
                path = filename
                if os.path.isfile(path.encode(encodeto)):
                        filename = os.path.basename(path)
 
                ctype = 'application/octet-stream'
                maintype, subtype = ctype.split('/', 1)
                fp = open(path.encode(encodeto), 'rb')
                fmsg = MIMEBase(maintype, subtype)
                fmsg.set_payload(fp.read())
                fp.close()
                email.Encoders.encode_base64(fmsg)
#               hd_fname = make_header([(filename,icharset)]) 
                fname = filename.encode(encodeto)
                fmsg.add_header('Content-Disposition', 'attachment', filename=fname)
                msg.attach(fmsg)
 
        srv = smtplib.SMTP(server, port)
        srv.ehlo()
        srv.sendmail(from_address, to, msg.as_string())
        srv.close()
 
# основной код скрипта
tempfile.tempdir = "/tmp"
tempname=tempfile.mktemp(".conv.tmp")
 
# тут запомним все временные файлы, чтобы потом их удалить
allfiles = []
# тут запомним сконвертированные файлы, чтобы потом их отправить
convfiles = []
 
# сохраним исходное письмо во временный файл
f = open(tempname, "w+b")
f.write(sys.stdin.read())
f.close()
allfiles.append(tempname)
f = open(tempname)
# читаем сообщение из файла
message=email.message_from_file(f)
f.close()
 
to = message['From']
subject = message['Subject']
 
# разбираем части сообщения
for part in message.walk():
        if part.is_multipart():
                continue
 
        #если часть сообщения — прикрепленный файл
        if part.get_filename() != None:
                (filename, fileext) = os.path.splitext(part.get_filename())
                if (fileext == '.docx') or (fileext == '.xlsx'):
                        doctype = fileext[1:4]
                        cfilter = convfilters[doctype]
                        docfile = '/tmp/'+filename+fileext
                        # сохраним вложенный файл
                        f = open(docfile.encode(encodeto), "wb")
                        f.write(part.get_payload(decode=True))
                        f.close()
                        allfiles.append(docfile)
                        # строка запуска LibreOffice
                        command = soffice+" --headless --convert-to "+cfilter+" --outdir /tmp \""+docfile+"\""
                        os.system(command.encode(encodeto))
                        newdocfile="/tmp/"+filename+"."+doctype
                        allfiles.append(newdocfile)
                        convfiles.append(newdocfile)
 
# если есть что отправлять, отправляем
if len(convfiles):
        sendmail(to,'Re:'+subject,'',convfiles)
 
# прибираем за собой
for filename in allfiles:
        os.remove(filename.encode(encodeto))

Использование скрипта

Согласно инструкции к smrsh, скрипт необходимо сохранить в каталоге /usr/adm/sm.bin/. В этот же каталог надо сделать симлинк на soffice.

В файл /etc/mail/aliases необходимо добавить строку:

conv:  |conv.py

и обновить базу алиасов с помощью команды newaliases.