Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advanced email parsing added; eg: Full Name email@domain.com #276

Merged
merged 6 commits into from
Aug 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions apprise/Apprise.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from .common import MATCH_ALL_TAG
from .utils import is_exclusive_match
from .utils import parse_list
from .utils import split_urls
from .utils import parse_urls
from .logger import logger

from .AppriseAsset import AppriseAsset
Expand Down Expand Up @@ -197,7 +197,7 @@ def add(self, servers, asset=None, tag=None):

if isinstance(servers, six.string_types):
# build our server list
servers = split_urls(servers)
servers = parse_urls(servers)
if len(servers) == 0:
return False

Expand Down
140 changes: 101 additions & 39 deletions apprise/plugins/NotifyEmail.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@
from ..common import NotifyFormat
from ..common import NotifyType
from ..utils import is_email
from ..utils import parse_list
from ..utils import GET_EMAIL_RE
from ..utils import parse_emails
from ..AppriseLocale import gettext_lazy as _

# Globally Default encoding mode set to Quoted Printable.
Expand Down Expand Up @@ -401,18 +400,20 @@ def __init__(self, timeout=15, smtp_host=None, from_name=None,
except (ValueError, TypeError):
self.timeout = self.connect_timeout

# Acquire targets
self.targets = parse_list(targets)
# Acquire Email 'To'
self.targets = list()

# Acquire Carbon Copies
self.cc = set()

# Acquire Blind Carbon Copies
self.bcc = set()

# For tracking our email -> name lookups
self.names = {}

# Now we want to construct the To and From email
# addresses from the URL provided
self.from_name = from_name
self.from_addr = from_addr

if self.user and not self.from_addr:
Expand All @@ -422,15 +423,18 @@ def __init__(self, timeout=15, smtp_host=None, from_name=None,
self.host,
)

if not is_email(self.from_addr):
result = is_email(self.from_addr)
if not result:
# Parse Source domain based on from_addr
msg = 'Invalid ~From~ email specified: {}'.format(self.from_addr)
self.logger.warning(msg)
raise TypeError(msg)

# If our target email list is empty we want to add ourselves to it
if len(self.targets) == 0:
self.targets.append(self.from_addr)
# Store our email address
self.from_addr = result['full_email']

# Set our from name
self.from_name = from_name if from_name else result['name']

# Now detect the SMTP Server
self.smtp_host = \
Expand All @@ -446,11 +450,35 @@ def __init__(self, timeout=15, smtp_host=None, from_name=None,
self.logger.warning(msg)
raise TypeError(msg)

# Validate recipients (cc:) and drop bad ones:
for recipient in parse_list(cc):
if targets:
# Validate recipients (to:) and drop bad ones:
for recipient in parse_emails(targets):
result = is_email(recipient)
if result:
self.targets.append(
(result['name'] if result['name'] else False,
result['full_email']))
continue

self.logger.warning(
'Dropped invalid To email '
'({}) specified.'.format(recipient),
)

if GET_EMAIL_RE.match(recipient):
self.cc.add(recipient)
else:
# If our target email list is empty we want to add ourselves to it
self.targets.append(
(self.from_name if self.from_name else False, self.from_addr))

# Validate recipients (cc:) and drop bad ones:
for recipient in parse_emails(cc):
email = is_email(recipient)
if email:
self.cc.add(email['full_email'])

# Index our name (if one exists)
self.names[email['full_email']] = \
email['name'] if email['name'] else False
continue

self.logger.warning(
Expand All @@ -459,10 +487,14 @@ def __init__(self, timeout=15, smtp_host=None, from_name=None,
)

# Validate recipients (bcc:) and drop bad ones:
for recipient in parse_list(bcc):

if GET_EMAIL_RE.match(recipient):
self.bcc.add(recipient)
for recipient in parse_emails(bcc):
email = is_email(recipient)
if email:
self.bcc.add(email['full_email'])

# Index our name (if one exists)
self.names[email['full_email']] = \
email['name'] if email['name'] else False
continue

self.logger.warning(
Expand Down Expand Up @@ -556,29 +588,51 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
# error tracking (used for function return)
has_error = False

if not self.targets:
# There is no one to email; we're done
self.logger.warning(
'There are no Email recipients to notify')
return False

# Create a copy of the targets list
emails = list(self.targets)
while len(emails):
# Get our email to notify
to_addr = emails.pop(0)

if not is_email(to_addr):
self.logger.warning(
'Invalid ~To~ email specified: {}'.format(to_addr))
has_error = True
continue
to_name, to_addr = emails.pop(0)

# Strip target out of cc list if in To or Bcc
cc = (self.cc - self.bcc - set([to_addr]))

# Strip target out of bcc list if in To
bcc = (self.bcc - set([to_addr]))

try:
# Format our cc addresses to support the Name field
cc = [formataddr(
(self.names.get(addr, False), addr), charset='utf-8')
for addr in cc]

# Format our bcc addresses to support the Name field
bcc = [formataddr(
(self.names.get(addr, False), addr), charset='utf-8')
for addr in bcc]

except TypeError:
# Python v2.x Support (no charset keyword)
# Format our cc addresses to support the Name field
cc = [formataddr(
(self.names.get(addr, False), addr)) for addr in cc]

# Format our bcc addresses to support the Name field
bcc = [formataddr(
(self.names.get(addr, False), addr)) for addr in bcc]

self.logger.debug(
'Email From: {} <{}>'.format(from_name, self.from_addr))
self.logger.debug('Email To: {}'.format(to_addr))
if len(cc):
if cc:
self.logger.debug('Email Cc: {}'.format(', '.join(cc)))
if len(bcc):
if bcc:
self.logger.debug('Email Bcc: {}'.format(', '.join(bcc)))
self.logger.debug('Login ID: {}'.format(self.user))
self.logger.debug(
Expand All @@ -597,13 +651,13 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
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')
base['To'] = formataddr((to_name, 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['To'] = formataddr((to_name, to_addr))

base['Cc'] = ','.join(cc)
base['Date'] = \
Expand Down Expand Up @@ -706,7 +760,6 @@ def url(self, privacy=False, *args, **kwargs):
# Define an URL parameters
params = {
'from': self.from_addr,
'name': self.from_name,
'mode': self.secure_mode,
'smtp': self.smtp_host,
'timeout': self.timeout,
Expand All @@ -716,13 +769,22 @@ def url(self, privacy=False, *args, **kwargs):
# Extend our parameters
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))

if self.from_name:
params['name'] = self.from_name

if len(self.cc) > 0:
# Handle our Carbon Copy Addresses
params['cc'] = ','.join(self.cc)
params['cc'] = ','.join(
['{}{}'.format(
'' if not e not in self.names
else '{}:'.format(self.names[e]), e) for e in self.cc])

if len(self.bcc) > 0:
# Handle our Blind Carbon Copy Addresses
params['bcc'] = ','.join(self.bcc)
params['bcc'] = ','.join(
['{}{}'.format(
'' if not e not in self.names
else '{}:'.format(self.names[e]), e) for e in self.bcc])

# pull email suffix from username (if present)
user = None if not self.user else self.user.split('@')[0]
Expand All @@ -748,7 +810,8 @@ def url(self, privacy=False, *args, **kwargs):
# a simple boolean check as to whether we display our target emails
# or not
has_targets = \
not (len(self.targets) == 1 and self.targets[0] == self.from_addr)
not (len(self.targets) == 1
and self.targets[0][1] == self.from_addr)

return '{schema}://{auth}{hostname}{port}/{targets}?{params}'.format(
schema=self.secure_protocol if self.secure else self.protocol,
Expand All @@ -758,7 +821,9 @@ def url(self, privacy=False, *args, **kwargs):
port='' if self.port is None or self.port == default_port
else ':{}'.format(self.port),
targets='' if not has_targets else '/'.join(
[NotifyEmail.quote(x, safe='') for x in self.targets]),
[NotifyEmail.quote('{}{}'.format(
'' if not e[0] else '{}:'.format(e[0]), e[1]),
safe='') for e in self.targets]),
params=NotifyEmail.urlencode(params),
)

Expand Down Expand Up @@ -792,8 +857,7 @@ def parse_url(url):

# Attempt to detect 'to' email address
if 'to' in results['qsd'] and len(results['qsd']['to']):
results['targets'] += \
NotifyEmail.parse_list(results['qsd']['to'])
results['targets'].append(results['qsd']['to'])

if 'name' in results['qsd'] and len(results['qsd']['name']):
# Extract from name to associate with from address
Expand All @@ -814,13 +878,11 @@ def parse_url(url):

# Handle Carbon Copy Addresses
if 'cc' in results['qsd'] and len(results['qsd']['cc']):
results['cc'] = \
NotifyEmail.parse_list(results['qsd']['cc'])
results['cc'] = results['qsd']['cc']

# Handle Blind Carbon Copy Addresses
if 'bcc' in results['qsd'] and len(results['qsd']['bcc']):
results['bcc'] = \
NotifyEmail.parse_list(results['qsd']['bcc'])
results['bcc'] = results['qsd']['bcc']

results['from_addr'] = from_addr
results['smtp_host'] = smtp_host
Expand Down