mirror of
https://github.com/caronc/apprise.git
synced 2025-01-06 05:59:23 +01:00
Better Email Internationalization Support (#270)
This commit is contained in:
parent
ad6316bda0
commit
0ef4e45fab
@ -29,6 +29,9 @@ import smtplib
|
|||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.mime.application import MIMEApplication
|
from email.mime.application import MIMEApplication
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.utils import formataddr
|
||||||
|
from email.header import Header
|
||||||
|
from email import charset
|
||||||
|
|
||||||
from socket import error as SocketError
|
from socket import error as SocketError
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@ -42,6 +45,9 @@ from ..utils import parse_list
|
|||||||
from ..utils import GET_EMAIL_RE
|
from ..utils import GET_EMAIL_RE
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
# Globally Default encoding mode set to Quoted Printable.
|
||||||
|
charset.add_charset('utf-8', charset.QP, charset.QP, 'utf-8')
|
||||||
|
|
||||||
|
|
||||||
class WebBaseLogin(object):
|
class WebBaseLogin(object):
|
||||||
"""
|
"""
|
||||||
@ -544,9 +550,8 @@ class NotifyEmail(NotifyBase):
|
|||||||
Perform Email Notification
|
Perform Email Notification
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from_name = self.from_name
|
# Initialize our default from name
|
||||||
if not from_name:
|
from_name = self.from_name if self.from_name else self.app_desc
|
||||||
from_name = self.app_desc
|
|
||||||
|
|
||||||
# error tracking (used for function return)
|
# error tracking (used for function return)
|
||||||
has_error = False
|
has_error = False
|
||||||
@ -581,15 +586,25 @@ class NotifyEmail(NotifyBase):
|
|||||||
|
|
||||||
# Prepare Email Message
|
# Prepare Email Message
|
||||||
if self.notify_format == NotifyFormat.HTML:
|
if self.notify_format == NotifyFormat.HTML:
|
||||||
content = MIMEText(body, 'html')
|
content = MIMEText(body, 'html', 'utf-8')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
content = MIMEText(body, 'plain')
|
content = MIMEText(body, 'plain', 'utf-8')
|
||||||
|
|
||||||
base = MIMEMultipart() if attach else content
|
base = MIMEMultipart() if attach else content
|
||||||
base['Subject'] = title
|
base['Subject'] = Header(title, 'utf-8')
|
||||||
base['From'] = '{} <{}>'.format(from_name, self.from_addr)
|
try:
|
||||||
base['To'] = to_addr
|
base['From'] = formataddr(
|
||||||
|
(from_name if from_name else False, self.from_addr),
|
||||||
|
charset='utf-8')
|
||||||
|
base['To'] = formataddr((False, to_addr), charset='utf-8')
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# Python v2.x Support (no charset keyword)
|
||||||
|
base['From'] = formataddr(
|
||||||
|
(from_name if from_name else False, self.from_addr))
|
||||||
|
base['To'] = formataddr((False, to_addr))
|
||||||
|
|
||||||
base['Cc'] = ','.join(cc)
|
base['Cc'] = ','.join(cc)
|
||||||
base['Date'] = \
|
base['Date'] = \
|
||||||
datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000")
|
datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000")
|
||||||
@ -623,7 +638,8 @@ class NotifyEmail(NotifyBase):
|
|||||||
app.add_header(
|
app.add_header(
|
||||||
'Content-Disposition',
|
'Content-Disposition',
|
||||||
'attachment; filename="{}"'.format(
|
'attachment; filename="{}"'.format(
|
||||||
attachment.name))
|
Header(attachment.name, 'utf-8')),
|
||||||
|
)
|
||||||
|
|
||||||
base.attach(app)
|
base.attach(app)
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com>
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
# This code is licensed under the MIT License.
|
# This code is licensed under the MIT License.
|
||||||
@ -28,6 +28,7 @@ import re
|
|||||||
import six
|
import six
|
||||||
import mock
|
import mock
|
||||||
import smtplib
|
import smtplib
|
||||||
|
from email.header import decode_header
|
||||||
|
|
||||||
from apprise import plugins
|
from apprise import plugins
|
||||||
from apprise import NotifyType
|
from apprise import NotifyType
|
||||||
@ -509,6 +510,79 @@ def test_smtplib_send_okay(mock_smtplib):
|
|||||||
AttachBase.max_file_size = max_file_size
|
AttachBase.max_file_size = max_file_size
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('smtplib.SMTP')
|
||||||
|
def test_smtplib_internationalization(mock_smtp):
|
||||||
|
"""
|
||||||
|
API: Test email handling using internationalization
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Disable Throttling to speed testing
|
||||||
|
plugins.NotifyBase.request_rate_per_sec = 0
|
||||||
|
|
||||||
|
# Defaults to HTML
|
||||||
|
obj = Apprise.instantiate(
|
||||||
|
'mailto://user:pass@gmail.com?name=Например%20так',
|
||||||
|
suppress_exceptions=False)
|
||||||
|
assert isinstance(obj, plugins.NotifyEmail)
|
||||||
|
|
||||||
|
class SMTPMock(object):
|
||||||
|
def sendmail(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
over-ride sendmail calls so we can check our our
|
||||||
|
internationalization formatting went
|
||||||
|
"""
|
||||||
|
|
||||||
|
match_subject = re.search(
|
||||||
|
r'\n?(?P<line>Subject: (?P<subject>(.+?)))\n(?:[a-z0-9-]+:)',
|
||||||
|
args[2], re.I | re.M | re.S)
|
||||||
|
assert match_subject is not None
|
||||||
|
|
||||||
|
match_from = re.search(
|
||||||
|
r'^(?P<line>From: (?P<name>.+) <(?P<email>[^>]+)>)$',
|
||||||
|
args[2], re.I | re.M)
|
||||||
|
assert match_from is not None
|
||||||
|
|
||||||
|
# Verify our output was correctly stored
|
||||||
|
assert match_from.group('email') == 'user@gmail.com'
|
||||||
|
|
||||||
|
if six.PY2: # Python 2.x (backwards compatible)
|
||||||
|
assert decode_header(match_from.group('name'))[0][0]\
|
||||||
|
.decode('utf-8') == u'Например так'
|
||||||
|
|
||||||
|
assert decode_header(match_subject.group('subject'))[0][0]\
|
||||||
|
.decode('utf-8') == u'دعونا نجعل العالم مكانا أفضل.'
|
||||||
|
|
||||||
|
else: # Python 3+
|
||||||
|
assert decode_header(match_from.group('name'))[0][0]\
|
||||||
|
.decode('utf-8') == 'Например так'
|
||||||
|
|
||||||
|
assert decode_header(match_subject.group('subject'))[0][0]\
|
||||||
|
.decode('utf-8') == 'دعونا نجعل العالم مكانا أفضل.'
|
||||||
|
|
||||||
|
# Dummy Function
|
||||||
|
def quit(self, *args, **kwargs):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Dummy Function
|
||||||
|
def starttls(self, *args, **kwargs):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Dummy Function
|
||||||
|
def login(self, *args, **kwargs):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Prepare our object we will test our generated email against
|
||||||
|
mock_smtp.return_value = SMTPMock()
|
||||||
|
|
||||||
|
# Further test encoding through the message content as well
|
||||||
|
assert obj.notify(
|
||||||
|
# Google Translated to Arabic: "Let's make the world a better place."
|
||||||
|
title='دعونا نجعل العالم مكانا أفضل.',
|
||||||
|
# Google Translated to Hungarian: "One line of code at a time.'
|
||||||
|
body='Egy sor kódot egyszerre.',
|
||||||
|
notify_type=NotifyType.INFO) is True
|
||||||
|
|
||||||
|
|
||||||
def test_email_url_escaping():
|
def test_email_url_escaping():
|
||||||
"""
|
"""
|
||||||
API: Test that user/passwords are properly escaped from URL
|
API: Test that user/passwords are properly escaped from URL
|
||||||
@ -536,7 +610,7 @@ def test_email_url_escaping():
|
|||||||
suppress_exceptions=False)
|
suppress_exceptions=False)
|
||||||
assert isinstance(obj, plugins.NotifyEmail) is True
|
assert isinstance(obj, plugins.NotifyEmail) is True
|
||||||
|
|
||||||
# The password is escapped 'once' at this point
|
# The password is escaped only 'once'
|
||||||
assert obj.password == ' %20'
|
assert obj.password == ' %20'
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user