Add MMS sending capacilities #1

Merged
alex merged 12 commits from dev into master 2021-05-21 05:57:05 +00:00
3 changed files with 85 additions and 95 deletions
Showing only changes of commit 213d73a475 - Show all commits

5
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"python.linting.flake8Enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.enabled": true
}

View file

@ -18,7 +18,6 @@ By default:
- python3 - python3
- python3-aiosmtpd - python3-aiosmtpd
- python3-pydbus - python3-pydbus
- python-messaging (pip install python-messaging)
### setup ### setup
Install the dependency and mms2mail (on debian based distribution): Install the dependency and mms2mail (on debian based distribution):
@ -26,7 +25,6 @@ Install the dependency and mms2mail (on debian based distribution):
sudo apt-get install python3 sudo apt-get install python3
sudo apt-get install python3-pydbus sudo apt-get install python3-pydbus
sudo apt-get install python3-aiosmtpd sudo apt-get install python3-aiosmtpd
pip install --user python-messaging
mkdir -p ~/.local/bin mkdir -p ~/.local/bin
cp mms2mail ~/.local/bin cp mms2mail ~/.local/bin
@ -66,23 +64,24 @@ port = 2525
### reference ### reference
``` ```
mms2mail [-h] [-d | -f FILES [FILES ...]] [--disable-smtp] [--disable-mms-delivery] [--delete] [--force-read] [--force-unlock] [-l {critical,error,warning,info,debug}] mms2mail [-h] [--disable-smtp] [--disable-mms-delivery] [--force-read] [--force-unlock] [-l {critical,error,warning,info,debug}]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-d, --daemon start in daemon mode
-f FILES [FILES ...], --file FILES [FILES ...]
Start in batch mode, parse specified mms files
--disable-smtp --disable-smtp
--disable-mms-delivery --disable-mms-delivery
--delete Ask mmsd to delete the converted MMS
--force-read Force conversion even if MMS is marked as read --force-read Force conversion even if MMS is marked as read
--force-unlock BEWARE COULD LEAD TO WHOLE MBOX CORRUPTION Force unlocking the mbox after a few minutes /!\ --force-unlock BEWARE COULD LEAD TO WHOLE MBOX CORRUPTION Force unlocking the mbox after a few minutes /!\
-l {critical,error,warning,info,debug}, --logging {critical,error,warning,info,debug} -l {critical,error,warning,info,debug}, --logging {critical,error,warning,info,debug}
Define the logger output level Define the logger output level
``` ```
### Using with Mutt : ### Sending MMS
To send MMS, mail address not in the following format would be ignored :
```+123456789@domain``` with phone number in international format.
#### with Mutt :
To be able to send mms with mutt you need it to be built with SMTP support. To be able to send mms with mutt you need it to be built with SMTP support.
And and the following line in your ```$HOME/.muttrc```: And and the following line in your ```$HOME/.muttrc```:
``` ```

132
mms2mail
View file

@ -9,12 +9,12 @@ import time
import logging import logging
from pathlib import Path from pathlib import Path
from messaging.mms.message import MMSMessage
import mailbox import mailbox
import email import email
from gi.repository import GLib from gi.repository import GLib
import pydbus from pydbus import SessionBus
from datetime import datetime
import os import os
import re import re
@ -87,13 +87,16 @@ class MMS2Mail:
""" """
self.dbus = dbusmmsd self.dbus = dbusmmsd
def check_mms(self, path): def check_mms(self, path, properties):
""" """
Check wether the provided file would be converted. Check wether the provided file would be converted.
:param path: the mms filesystem path :param path: the mms filesystem path
:type path: str :type path: str
:param properties: the mms properties
:type properties: Array
:return the mms status or None :return the mms status or None
:rtype str :rtype str
""" """
@ -121,7 +124,7 @@ class MMS2Mail:
else: else:
log.debug(f"New outgoing MMS found ({name.split('/')[-1]})") log.debug(f"New outgoing MMS found ({name.split('/')[-1]})")
def convert(self, path, dbus_path=None, properties=None): def convert(self, path, dbus_path, properties):
""" """
Convert a provided mms file to a mail stored in a mbox. Convert a provided mms file to a mail stored in a mbox.
@ -130,80 +133,77 @@ class MMS2Mail:
:param dbus_path: the mms dbus path :param dbus_path: the mms dbus path
:type dbus_path: str :type dbus_path: str
:param properties: the mms properties
:type properties: Array
""" """
# Check if the provided file present # Check if the provided file present
status = self.check_mms(path) status = self.check_mms(path, properties)
if not status: if not status:
log.error("MMS file not convertible.") log.error("MMS file not convertible.")
return return
# Generate its dbus path, for future operation (mark as read, delete)
if not dbus_path:
dbus_path = f"/org/ofono/mms/modemmanager/{path.split('/')[-1]}"
mms = MMSMessage.from_file(path)
message = email.message.EmailMessage() message = email.message.EmailMessage()
# Generate Mail Headers # Generate Mail Headers
mms_h_from = mms.headers.get('From', 'unknown/undef') mms_from = properties.get('Sender', "unknown")
log.debug(f"MMS[From]: {mms_h_from}") log.debug(f"MMS[From]: {mms_from}")
if 'not inserted' in mms_h_from: if '@' in mms_from:
mms_h_from = 'unknown/undef' message['From'] = mms_from
mms_from, mms_from_type = mms_h_from.split('/') else:
message['From'] = f"{mms_from}@{self.domain}" message['From'] = f"{mms_from}@{self.domain}"
mms_h_to = mms.headers.get('To', 'unknown/undef') to = properties.get('Modem Number', None)
log.debug(f"MMS[To]: {mms_h_to}") if to:
if 'not inserted' in mms_h_to: message['To'] = f"{mms_from}@{self.domain}"
mms_h_to = 'unknown/undef' recipients = ""
mms_to, mms_to_type = mms_h_to.split('/')
message['To'] = f"{mms_to}@{self.domain}"
# Get other recipients from dbus signal
# https://github.com/pmarti/python-messaging/issues/49
if properties:
cc = ""
for r in properties['Recipients']: for r in properties['Recipients']:
if mms_to in r: if to and to in r:
continue continue
log.debug(f'MMS/MAIL CC : {r}') log.debug(f'MMS[CC] : {r}')
cc += f"{r}@{self.domain}," if '@' in r:
if cc: recipients += f"{r},"
cc = cc[:-1]
message['CC'] = cc
if 'Subject' in mms.headers and mms.headers['Subject']:
message['Subject'] = mms.headers['Subject']
else: else:
if status == 'sent' or status == 'draft': recipients += f"{r}@{self.domain},"
message['Subject'] = f"MMS to {mms_to}" if recipients:
recipients = recipients[:-1]
if to:
message['CC'] = recipients
else: else:
message['Subject'] = f"MMS from {mms_from}" message['To'] = recipients
if 'Date' in mms.headers and mms.headers['Date']: message['Subject'] = properties.get('Subject',
message['Date'] = mms.headers['Date'] f"MMS from {mms_from}")
mms_date = properties.get('Date')
# Recopy MMS HEADERS if mms_date:
for header in mms.headers: mms_datetime = datetime.strptime(mms_date, '%Y-%m-%dT%H:%M:%S%z')
message.add_header(f"X-MMS-{header}", f"{mms.headers[header]}") mail_date = email.utils.format_datetime(mms_datetime)
message['Date'] = mail_date or email.utils.formatdate()
message.preamble = "This mail is converted from a MMS." message.preamble = "This mail is converted from a MMS."
body = "" body = ""
data_id = 1
attachments = [] attachments = []
for data_part in mms.data_parts: for attachment in properties['Attachments']:
datacontent = data_part.headers['Content-Type'] cid = attachment[0]
if datacontent is not None: mimetype = attachment[1]
maintype, subtype = datacontent[0].split('/', 1) contentfile = attachment[2]
if 'text/plain' in datacontent[0]: offset = attachment[3]
encoding = datacontent[1].get('Charset', 'utf-8') size = attachment[4]
body += data_part.data.decode(encoding, with open(contentfile, 'rb') as f:
f.seek(offset, 0)
content = f.read(size)
if mimetype is not None:
if 'text/plain' in mimetype:
mimetype, charset = mimetype.split(';', 1)
encoding = charset.split('=')[1]
body += content.decode(encoding,
errors='replace') + '\n' errors='replace') + '\n'
continue continue
extension = str(mimetypes.guess_extension(datacontent[0])) maintype, subtype = mimetype.split('/', 1)
filename = datacontent[1].get('Name', str(data_id)) extension = str(mimetypes.guess_extension(mimetype))
attachments.append([data_part.data, maintype, filename = cid
attachments.append([content, maintype,
subtype, filename + extension]) subtype, filename + extension])
data_id = data_id + 1
if body: if body:
message.set_content(body) message.set_content(body)
for a in attachments: for a in attachments:
@ -212,7 +212,8 @@ class MMS2Mail:
subtype=a[2], subtype=a[2],
filename=a[3]) filename=a[3])
# Add MMS binary file, for debugging purpose or reparsing in the future # Add MMS binary file, for debugging purpose
# or reparsing in the future
if self.attach_mms: if self.attach_mms:
with open(path, 'rb') as fp: with open(path, 'rb') as fp:
message.add_attachment(fp.read(), message.add_attachment(fp.read(),
@ -385,7 +386,7 @@ class DbusMMSd():
:type mms2mail: mms2mail() :type mms2mail: mms2mail()
""" """
self.mms2mail = mms2mail self.mms2mail = mms2mail
self.bus = pydbus.SessionBus() self.bus = SessionBus()
def set_mms2mail(self, mms2mail): def set_mms2mail(self, mms2mail):
""" """
@ -534,13 +535,6 @@ class DbusMMSd():
def main(): def main():
"""Run the different functions handling mms and mail.""" """Run the different functions handling mms and mail."""
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
mode = parser.add_mutually_exclusive_group()
mode.add_argument("-d", "--daemon",
help="start in daemon mode ",
action='store_true', dest='daemon')
mode.add_argument("-f", "--file", nargs='+',
help="Start in batch mode, parse specified mms files",
dest='files')
parser.add_argument('--disable-smtp', action='store_true', parser.add_argument('--disable-smtp', action='store_true',
dest='disable_smtp') dest='disable_smtp')
parser.add_argument('--disable-mms-delivery', action='store_true', parser.add_argument('--disable-mms-delivery', action='store_true',
@ -585,12 +579,7 @@ def main():
force_unlock=args.force_unlock) force_unlock=args.force_unlock)
m.set_dbus(d) m.set_dbus(d)
if args.files: log.info("Starting mms2mail")
for mms_file in args.files:
m.convert(path=mms_file)
return
elif args.daemon:
log.info("Starting mms2mail in daemon mode")
if not args.disable_smtp: if not args.disable_smtp:
log.info("Activating smtp to mmsd server") log.info("Activating smtp to mmsd server")
controller.start() controller.start()
@ -599,9 +588,6 @@ def main():
d.set_mms2mail(m) d.set_mms2mail(m)
d.add_signal_receiver() d.add_signal_receiver()
m.convert_stored_mms() m.convert_stored_mms()
else:
parser.print_help()
return
d.run() d.run()
controller.stop() controller.stop()