#! /usr/bin/env python3 import json import smtplib import ssl from email import utils from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart import pypandoc import yaml import argparse import certifi import os import requests import re import datetime # only for developing endDate = (datetime.datetime.now() + datetime.timedelta(days=7)).strftime("%Y-%m-%d") participoUrl = "http://cwsvjudo.bplaced.net/participo" class Email: title = None mdText = None def __init__(self, title, mdText): self.title = title self.mdText = mdText def addApiKeyToUrls(self, apiKey, url=participoUrl): self.mdText = re.sub(url+"([^#,\n]*)", url + "\\1" + "?apiKey="+apiKey, self.mdText) @staticmethod def loadFromMdFile(path): # read markdownfile as header and text mdHeader = None with open(argv.mdFilePath) as f: mdHeader = yaml.safe_load(get_yaml(f)) title = mdHeader['title'] if checkHeader( mdHeader) else "cwsvJudo newsLetter" mdText = None with open(argv.mdFilePath) as f: mdText = f.read() return Email(title, mdText) def get_yaml(f): '''Extracts the yamlHeader from a Markdown file''' yamlHeader = None pointer = f.tell() if f.readline() != '---\n': f.seek(pointer) return '' readline = iter(f.readline, '') readline = iter(readline.__next__, '---\n') yamlHeader = ''.join(readline) return yamlHeader def checkHeader(header): """check the header for validity useful, if the title was forgotten Args: header (dict): yamlHeader of the mdNewsletter Returns: bool: true if header is alright, false if an error was detected """ retVal = True if not 'title' in header: print("Header has no 'title' attribute") retVal = False else: if header['title'] is False: print("Empty title!") retVal = False return retVal def getArguments(): argParser = argparse.ArgumentParser( description="Send an Markdown-File as eMail" ) argParser.add_argument( "mdFilePath", help="Path to MarkdownFile to send" ) argParser.add_argument( "-r", "--receiver", help="json file with the receiver") return argParser.parse_args() def loadFromJson(path): jsonDict = None with open(argv.receiver) as jsonFile: jsonDict = json.load(jsonFile) return jsonDict def loadFromYaml(path): yamlDict = None with open(path, "r") as yamlFile: yamlDict = yaml.safe_load(yamlFile) return yamlDict def createApiKey(allowKey, userId, rights, endDate): """call the participo api to create a new api key Args: allowKey (string): api key that allows the creation apiKey-s userId (int): id of the receiver of the new api key rights (string): comma separated list of strings denoting the rights of the api key endDate (string): 'yyyy-mm-dd' formated string denoting the rights of the api key return: new api key or none on failure """ postData = { 'apiKey': allowKey, 'userId': userId, 'rights': rights, 'endDate': endDate } response = requests.post( participoUrl+"/api.apiKeys.add", json=postData) jsonResponse = response.json() return jsonResponse['apiKey'] if 'apiKey' else None if __name__ == "__main__": argv = getArguments() config = loadFromYaml("./config.yaml") receivers = loadFromJson(argv.receiver) emailTemplate = Email.loadFromMdFile(argv.mdFilePath) # messages are prepared emails ready to send messageQueue = [] for user in receivers: apiKey = createApiKey( config['apiKey']['createApiKey'], user['id'], "login", endDate) if apiKey is None: print(f"Couldn't get apiKey") continue email = Email.loadFromMdFile(argv.mdFilePath) # emailTemplate email.addApiKeyToUrls(apiKey) # Create the plain-text and HTML version of your message # text = pypandoc.convert_text(email.mdText, "plain", format='md', extra_args=[ # "--self-contained", "--resource-path=../aufgaben"]) # plain text did swallow the Url. As workaround we are trying markdown for the plain text text = pypandoc.convert_text(email.mdText, "markdown", format='md', extra_args=[ "--self-contained", "--resource-path=../aufgaben"]) html = pypandoc.convert_text(email.mdText, "html", format='md', extra_args=[ "--self-contained", "--resource-path=../aufgaben"]) # Turn these into plain/html MIMEText objects txtMimeText = MIMEText(text, "plain") htmlMimeText = MIMEText(html, "html") # @todo the message has to be recreated for every email address since it seems to be added by reference to the queue. meaning changing the emailAddress changes it in the previously added also for toAddress in user['eMail']: # create the mail message = MIMEMultipart("alternative") # Setting header data message["Subject"] = email.title message["From"] = config['senderEmailAddress'] message["Reply-To"] = config['senderEmailAddress'] message["Date"] = str(utils.formatdate(localtime=True)) # only set the to-header one time: setting it multiple # times results in a multiple to-entries in the header! # Meaning the mail has to be recreated for each to address. # @todo Find a way to reuse the created mail for every recipent message["To"] = toAddress # Add HTML/plain-text parts to MIMEMultipart message # The email client will try to render the last part first message.attach(htmlMimeText) message.attach(txtMimeText) messageQueue.append(message) # Create a secure SSL context context = ssl.create_default_context() # @todo This is a very bad hack, because the cert checking doesn't work anymore context.check_hostname = False context.verify_mode = ssl.CERT_NONE with smtplib.SMTP_SSL(config['smtp']['serverAddress'], config['smtp']['serverPort'], context=context) as server: server.login(config['smtp']['login'], config['smtp']['password']) for message in messageQueue: print(f"sending to {message['To']}") server.sendmail(config['senderEmailAddress'], message["To"], message.as_string())