mirror of
https://github.com/caronc/apprise.git
synced 2025-01-03 20:49:19 +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.application import MIMEApplication
|
||||
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 datetime import datetime
|
||||
@ -42,6 +45,9 @@ from ..utils import parse_list
|
||||
from ..utils import GET_EMAIL_RE
|
||||
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):
|
||||
"""
|
||||
@ -544,9 +550,8 @@ class NotifyEmail(NotifyBase):
|
||||
Perform Email Notification
|
||||
"""
|
||||
|
||||
from_name = self.from_name
|
||||
if not from_name:
|
||||
from_name = self.app_desc
|
||||
# Initialize our default from name
|
||||
from_name = self.from_name if self.from_name else self.app_desc
|
||||
|
||||
# error tracking (used for function return)
|
||||
has_error = False
|
||||
@ -581,15 +586,25 @@ class NotifyEmail(NotifyBase):
|
||||
|
||||
# Prepare Email Message
|
||||
if self.notify_format == NotifyFormat.HTML:
|
||||
content = MIMEText(body, 'html')
|
||||
content = MIMEText(body, 'html', 'utf-8')
|
||||
|
||||
else:
|
||||
content = MIMEText(body, 'plain')
|
||||
content = MIMEText(body, 'plain', 'utf-8')
|
||||
|
||||
base = MIMEMultipart() if attach else content
|
||||
base['Subject'] = title
|
||||
base['From'] = '{} <{}>'.format(from_name, self.from_addr)
|
||||
base['To'] = to_addr
|
||||
base['Subject'] = Header(title, 'utf-8')
|
||||
try:
|
||||
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['Date'] = \
|
||||
datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000")
|
||||
@ -623,7 +638,8 @@ class NotifyEmail(NotifyBase):
|
||||
app.add_header(
|
||||
'Content-Disposition',
|
||||
'attachment; filename="{}"'.format(
|
||||
attachment.name))
|
||||
Header(attachment.name, 'utf-8')),
|
||||
)
|
||||
|
||||
base.attach(app)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
||||
# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
@ -28,6 +28,7 @@ import re
|
||||
import six
|
||||
import mock
|
||||
import smtplib
|
||||
from email.header import decode_header
|
||||
|
||||
from apprise import plugins
|
||||
from apprise import NotifyType
|
||||
@ -509,6 +510,79 @@ def test_smtplib_send_okay(mock_smtplib):
|
||||
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():
|
||||
"""
|
||||
API: Test that user/passwords are properly escaped from URL
|
||||
@ -536,7 +610,7 @@ def test_email_url_escaping():
|
||||
suppress_exceptions=False)
|
||||
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'
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user