Add MMS sending capacilities #1
3 changed files with 85 additions and 95 deletions
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"python.linting.flake8Enabled": true,
|
||||||
|
"python.linting.pylintEnabled": false,
|
||||||
|
"python.linting.enabled": true
|
||||||
|
}
|
15
README.md
15
README.md
|
@ -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```:
|
||||||
```
|
```
|
||||||
|
|
160
mms2mail
160
mms2mail
|
@ -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
|
||||||
|
@ -69,7 +69,7 @@ class MMS2Mail:
|
||||||
self.attach_mms = cfg.getboolean('mail', 'attach_mms',
|
self.attach_mms = cfg.getboolean('mail', 'attach_mms',
|
||||||
fallback=False)
|
fallback=False)
|
||||||
self.delete = cfg.getboolean('mail', 'delete_from_mmsd',
|
self.delete = cfg.getboolean('mail', 'delete_from_mmsd',
|
||||||
fallback=False)
|
fallback=False)
|
||||||
self.domain = cfg.get('mail', 'domain',
|
self.domain = cfg.get('mail', 'domain',
|
||||||
fallback=socket.getfqdn())
|
fallback=socket.getfqdn())
|
||||||
self.user = cfg.get('mail', 'user', fallback=getpass.getuser())
|
self.user = cfg.get('mail', 'user', fallback=getpass.getuser())
|
||||||
|
@ -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('/')
|
|
||||||
message['From'] = f"{mms_from}@{self.domain}"
|
|
||||||
|
|
||||||
mms_h_to = mms.headers.get('To', 'unknown/undef')
|
|
||||||
log.debug(f"MMS[To]: {mms_h_to}")
|
|
||||||
if 'not inserted' in mms_h_to:
|
|
||||||
mms_h_to = 'unknown/undef'
|
|
||||||
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']:
|
|
||||||
if mms_to in r:
|
|
||||||
continue
|
|
||||||
log.debug(f'MMS/MAIL CC : {r}')
|
|
||||||
cc += f"{r}@{self.domain},"
|
|
||||||
if cc:
|
|
||||||
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':
|
message['From'] = f"{mms_from}@{self.domain}"
|
||||||
message['Subject'] = f"MMS to {mms_to}"
|
|
||||||
|
to = properties.get('Modem Number', None)
|
||||||
|
if to:
|
||||||
|
message['To'] = f"{mms_from}@{self.domain}"
|
||||||
|
recipients = ""
|
||||||
|
for r in properties['Recipients']:
|
||||||
|
if to and to in r:
|
||||||
|
continue
|
||||||
|
log.debug(f'MMS[CC] : {r}')
|
||||||
|
if '@' in r:
|
||||||
|
recipients += f"{r},"
|
||||||
else:
|
else:
|
||||||
message['Subject'] = f"MMS from {mms_from}"
|
recipients += f"{r}@{self.domain},"
|
||||||
|
if recipients:
|
||||||
|
recipients = recipients[:-1]
|
||||||
|
if to:
|
||||||
|
message['CC'] = recipients
|
||||||
|
else:
|
||||||
|
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:
|
||||||
errors='replace') + '\n'
|
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'
|
||||||
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,23 +579,15 @@ 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:
|
if not args.disable_smtp:
|
||||||
m.convert(path=mms_file)
|
log.info("Activating smtp to mmsd server")
|
||||||
return
|
controller.start()
|
||||||
elif args.daemon:
|
if not args.disable_mms_delivery:
|
||||||
log.info("Starting mms2mail in daemon mode")
|
log.info("Activating mms to mbox server")
|
||||||
if not args.disable_smtp:
|
d.set_mms2mail(m)
|
||||||
log.info("Activating smtp to mmsd server")
|
d.add_signal_receiver()
|
||||||
controller.start()
|
m.convert_stored_mms()
|
||||||
if not args.disable_mms_delivery:
|
|
||||||
log.info("Activating mms to mbox server")
|
|
||||||
d.set_mms2mail(m)
|
|
||||||
d.add_signal_receiver()
|
|
||||||
m.convert_stored_mms()
|
|
||||||
else:
|
|
||||||
parser.print_help()
|
|
||||||
return
|
|
||||||
|
|
||||||
d.run()
|
d.run()
|
||||||
controller.stop()
|
controller.stop()
|
||||||
|
|
Loading…
Reference in a new issue