I was trying to work out if I could use this to merge args and params to give me my combined data. Is this possible?
Any other suggestions to tidy my code? I’m happy with the result but always worth looking at better ways for future projects.
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import smtplib
import ssl
import argparse
from time import sleep
from datetime import datetime
import json
def send_mail():
SSLcontext = ssl.create_default_context()
if verbose: print(f"server: {sending_server} port: {port}")
with smtplib.SMTP(sending_server, port) as server:
if verbose: server.set_debuglevel(1) # set diagnostics to verbose
server.starttls(context=SSLcontext)
if not auth_password:
if verbose: print("no authentication")
else:
server.login(auth_username, auth_password)
server.sendmail(from_envelope, recipient, text)
if not quiet: print(f"{datetime.now():%Y-%m-%d %H:%M} - e-mail sent from {from_envelope} to {recipient} with subject: {subject}")
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description="""Davo's emailer v2
This program is aimed at helping the user test mailservers.
I've tried to include all scenarios but happy to add more options if requested (and it's possible).
Note that the parameters file is required to list sending_server and port if these are not defined
via the command line arguments (-s and -p).
The default parameters file is emailer_params.json and needs at a minimum the following:
{"port": 25,
"sending_server": "<mailserver to send to>"}""", epilog="""
This program is not for commercial modification or resale without permission from me.
Permission is granted for commercial or personal use.""")
parser.add_argument("-F", "--file_import", type=str, metavar="", default="emailer_params.json",
help="user defined parameters file otherwise emailer_params.json will be used, parameters set via cmd line will take priority")
parser.add_argument("-r", "--recipient", type=str, metavar="",
help="recipient address - REQUIRED if not imported from file")
parser.add_argument("-f", "--from_envelope", type=str, metavar="",
help="from address - envelope - REQUIRED if not imported from file")
parser.add_argument("-fh", "--from_header", type=str, metavar="",
help="from address - header (i.e. if not specified will be same as from-envelope")
parser.add_argument("-p", "--port", type=int, metavar="",
help="SMTP port to use (default set in params json)")
parser.add_argument("-s", "--sending_server", type=str, metavar="",
help="destination SMTP server (default set in params json)")
parser.add_argument("-j", "--subject", type=str, metavar="", help="message subject")
parser.add_argument("-t", "--message_text", type=str, metavar="", help="message text - single line")
parser.add_argument("-au", "--auth_username", type=str, metavar="",
help="authentication username - optional, if not used from_envelope address will be used")
parser.add_argument("-ap", "--auth_password", type=str, metavar="",
help="authentication password - optional, note if this is not specified no authentication will be used even if -au is set")
parser.add_argument("-l", "--loop", type=int, metavar="",
help="continuously loop - variable is time between sending in seconds")
parser.add_argument("-T", "--message_text_input", action="store_true",
help="Input message text - multi-line - flag")
parser.add_argument("-E", "--error_handling", action="store_true",
help="Error handling - not normally recommended but will ensure it will keep looping even on an error - flag")
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true",
help="verbose diagnostics - flag - NB: -v or -q")
group.add_argument("-q", "--quiet", action="store_true",
help="quiet - no output - flag - NB: -v or -q")
args = parser.parse_args()
file_import = args.file_import
print(f"parameters file used: {file_import}")
#import_file - either default or defined
try:
with open(file_import, 'r') as openfile:
params = json.load(openfile)
except FileNotFoundError:
print("parameters file does not exist")
quit()
except Exception as err:
print(f"error in import - probably incorrectly defined variable. error code: {err}")
quit()
# import data from either argpass (command line) or parameters file
# priority is given to argpass so if that is defined the value will not be imported from the parameters file
if args.recipient: recipient = args.recipient
else: recipient = params.get("recipient")
if args.from_header: from_header = args.from_header
else: from_header = params.get("from_header")
if args.from_envelope: from_envelope = args.from_envelope
else: from_envelope = params.get("from_envelope")
if args.port: port = args.port
else: port = params.get("port")
if args.sending_server: sending_server = args.sending_server
else: sending_server = params.get("sending_server")
if args.subject: subject = args.subject
else: subject = params.get("subject")
if args.message_text: message_text = args.message_text
else: message_text = params.get("message_text")
if args.auth_username: auth_username = args.auth_username
else: auth_username = params.get("auth_username")
if args.auth_password: auth_password = args.auth_password
else: auth_password = params.get("auth_password")
if args.loop: loop = args.loop
else: loop = params.get("loop")
if args.message_text_input: message_text_input = args.message_text_input
else: message_text_input = params.get("message_text_input")
if args.error_handling: error_handling = args.error_handling
else: error_handling = params.get("error_handling")
if args.verbose: verbose = args.verbose
else: verbose = params.get("verbose")
if args.quiet: quiet = args.quiet
else: quiet = params.get("quiet")
if not quiet: print(f"quiet: {quiet}, verbose: {verbose}")
# Logic checking of variables and rules in place
# quiet and verbose are mutually exclusive
if (quiet and verbose): raise ValueError("quiet and verbose cannot be set at the same time. check parameters file")
# recipient - must be defined by argument or parameters file
if not recipient: raise ValueError("recipient must be defined")
# note the from envelope is what is in the initial connection (i.e. MAIL FROM: from_envelope)
# note the from header is what shows in the e-mail client. This is often faked.
# if the from_header isn't specified we assume it's the same as the from_envelope
# define from_envelope - must be defined by argument or parameters file
if not from_envelope: raise ValueError("from_envelope must be defined")
# define from_header. if not defined by params or argument use from_envelope
if not from_header: from_header = from_envelope
# subject and message text need to be defined or there will be an error when sending
if not subject: subject = ""
if message_text:
message = message_text
else:
message = ""
# if auth_password specified authentication will be attempted
# in this case the auth_username will be used. If not specified the from_envelope address will be used as auth_username
if auth_password and not auth_username: auth_username = from_envelope
if verbose: print(f"recipient: {recipient}, from envelope: {from_envelope}, from header: {from_header}")
if verbose: print(f"sending server: {sending_server}, port: {port}")
if verbose: print(f"subject: {subject}")
if verbose: print(f"auth_username: {auth_username}, auth_password: {auth_password}")
if verbose: print(f"loop: {loop}")
# quit()
if message_text_input:
print("Enter message, end with a blank lines (i.e. enter twice)")
message = ""
counter = 0
while True:
line = input()
if line.strip() == '':
counter += 1
if counter == 2:
break
# \n is new line, %s is string formatting
# str.strip() will remove any whitespace that is at start or end
message += "%s\n" % line.strip()
if verbose: print(f"message: {message}, length: {len(message)}")
msg = MIMEMultipart()
msg['From']= from_header
msg['To']= recipient
msg['Subject']= subject
# removed - not implementing bcc cc due to issues and it's not really what I need
# if args.bcc: msg["Bcc"] = args.bcc
#if args.cc: msg["Cc"] = args.cc
if verbose: print("This is our message format: ", msg)
if verbose: print("This is the message envelope: ", "From: ", from_envelope, "To: ", recipient)
msg.attach(MIMEText(message, "plain"))
text = msg.as_string()
if verbose: print("This is the text field")
if verbose: print(text)
# if -l loop parameter set the send_mail function will run continuously
if loop:
if error_handling: #this will keep the script working if an error occurs
if not quiet: print("Error handling active")
while True:
try:
send_mail()
if verbose: print(f"Waiting for {loop} seconds")
sleep(loop)
except Exception as err:
print(f"""{datetime.now():%Y-%m-%d %H:%M} - An error occurred
- for more detailed info on the error run with the -v (verbose) switch
- the error message was
{err}""")
if verbose: print(f"Waiting for {loop} seconds")
sleep(loop)
else:
while True:
send_mail()
if verbose: print(f"Waiting for {loop} seconds")
sleep(loop)
else:
send_mail()
quit()