From 7331394ecaea9839e8ea41f0ddf301cf45855156 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 16 Mar 2020 14:02:33 -0400 Subject: [PATCH 01/12] More secure version --- assets/py/emailUpdateScript.py | 133 +++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100755 assets/py/emailUpdateScript.py diff --git a/assets/py/emailUpdateScript.py b/assets/py/emailUpdateScript.py new file mode 100755 index 000000000..ece106bb5 --- /dev/null +++ b/assets/py/emailUpdateScript.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# This Python file uses the following encoding: utf-8 +import json, schedule, smtplib, time +import urllib.request +from datetime import date, timedelta, datetime +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from messageTemplates import Templates +############################################################################### +###From ore-ero folder, run with ./assets/py/emailUpdateScript.py ### +############################################################################### +##Replace with whichever gmail will be used to send the messages, can use this one for tests +sender = "scheduledupdatescripttester@gmail.com" +#App password for the gmail +password = "koppttrkbyodglmc" +#Maximum amount of days since last update, half a year default +maxDaysNoUpdate = 182 +##For the scheduler +#weeksToExec if we want to make sure it's done at a specific day of the week, half a year default +weeksToExec = 26 +#daysToExec can be changed to whatever number as long as it's long enough not to spam contacts, half a year default +daysToExec = 182 +#execTime doesn't really matter considering speed of execution, default at midnight +#To test it change this to a time at least 1 minute after your current time +execTime = "00:00" + +def sendEmails(emailData): + server = smtplib.SMTP_SSL(host='smtp.gmail.com') + server.login(sender, password) + for data in emailData: + email = MIMEMultipart() + email['from'] = sender + ##Replace with data[0] to actually send the mails to the right place + email['to'] = "Simon_moreau@hotmail.ca" + email['subject'] = Templates.getSubject() + plainVersion = MIMEText(Templates.plainFormat(data[1], data[2], data[3]), 'plain') + plainVersion.add_header("Content-Disposition", + "attachment; filename= Plain Text Version.txt") + htmlVersion = MIMEText(Templates.htmlFormat(data[1], data[2], data[3]), 'html') + email.attach(htmlVersion) + email.attach(plainVersion) + server.send_message(email) + del email + ##limit how many emails are sent during testing + break + server.quit() + + +def checkCodeEmails(): + codeDb = urllib.request.urlopen("https://canada-ca.github.io/ore-ero/code.json") + data = json.loads(codeDb.read()) + codeData = [] + if data is not None: + for level in data.values(): + for admin in level.values(): + for release in admin["releases"]: + if (datetime.strptime(release["date"]["metadataLastUpdated"], '%Y-%m-%d').date() + + timedelta(days=maxDaysNoUpdate) < date.today()): + codeData.append((release["contact"]["email"], release["name"]["en"], + release["name"]["fr"], release["date"]["metadataLastUpdated"])) + return codeData + + +def checkDesignEmails(): + designDb = urllib.request.urlopen("https://canada-ca.github.io/ore-ero/design.json") + data = json.loads(designDb.read()) + designData = [] + if data is not None: + for project in data.values(): + for administration in project["administrations"]: + for use in administration["uses"]: + if (datetime.strptime(use["date"]["metadataLastUpdated"], '%Y-%m-%d').date() + + timedelta(days=maxDaysNoUpdate) < date.today()): + designData.append((use["contact"]["email"], project["name"]["en"], + project["name"]["fr"], use["date"]["metadataLastUpdated"])) + return designData + +def checkSoftwareEmails(): + softwareDb = urllib.request.urlopen("https://canada-ca.github.io/ore-ero/software.json") + data = json.loads(softwareDb.read()) + softwareData = [] + if data is not None: + for project in data.values(): + for administration in project["administrations"]: + for use in administration["uses"]: + if (datetime.strptime(use["date"]["metadataLastUpdated"], '%Y-%m-%d').date() + + timedelta(days=maxDaysNoUpdate) < date.today()): + softwareData.append((use["contact"]["email"], project["name"]["en"], + project["name"]["fr"], use["date"]["metadataLastUpdated"])) + return softwareData + +def checkStandardEmails(): + standardDb = urllib.request.urlopen("https://canada-ca.github.io/ore-ero/standard.json") + data = json.loads(standardDb.read()) + standardData = [] + if data is not None: + for project in data.values(): + for administration in project["administrations"]: + if (datetime.strptime(administration["date"]["metadataLastUpdated"], '%Y-%m-%d').date() + + timedelta(days=maxDaysNoUpdate) < date.today()): + standardData.append((administration["contact"]["email"], project["standardAcronym"], + project["standardAcronym"], administration["date"]["metadataLastUpdated"])) + return standardData + +def checkPartnershipEmails(): + partnershipDb = urllib.request.urlopen("https://canada-ca.github.io/ore-ero/partnership.json") + data = json.loads(partnershipDb.read()) + partnershipData = [] + if data is not None: + for level in data.values(): + for admin in level.values(): + for project in admin["projects"]: + if (datetime.strptime(project["date"]["metadataLastUpdated"], '%Y-%m-%d').date() + + timedelta(days=maxDaysNoUpdate) < date.today()): + partnershipData.append((project["contact"]["email"], project["name"]["en"], + project["name"]["fr"], project["date"]["metadataLastUpdated"])) + return partnershipData + +def checkOutdatedEmails(): + print("Started task at: " + datetime.now().isoformat(' ', 'seconds')) + sendEmails(checkCodeEmails() + checkDesignEmails() + checkSoftwareEmails() + + checkStandardEmails() + checkPartnershipEmails()) + print("Finished task at: " + datetime.now().isoformat(' ', 'seconds')) + +#Implementation +##schedule.every(weeksToExec).weeks.at(execTime).do(checkOutdatedEmails) +##schedule.every(daysToExec).days.at(execTime).do(checkOutdatedEmails) +#For testing +schedule.every().day.at(execTime).do(checkOutdatedEmails) + +while True: + schedule.run_pending() + time.sleep(1) \ No newline at end of file From 1d39513e4d90d06678c8101ef901ace8e8f90064 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 16 Mar 2020 14:27:29 -0400 Subject: [PATCH 02/12] Re-added templates --- assets/py/messageTemplates.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 assets/py/messageTemplates.py diff --git a/assets/py/messageTemplates.py b/assets/py/messageTemplates.py new file mode 100644 index 000000000..df9336445 --- /dev/null +++ b/assets/py/messageTemplates.py @@ -0,0 +1,31 @@ +class Templates: + plain = """ + English Message: + Test message about {EN_NAME}, last updated {LAST_UPDATED} + "https://code.ouvert.canada.ca/en/index.html" + + Message en français: + Message test concernant {FR_NAME}, mise à jour la plus récente {LAST_UPDATED} + "https://code.ouvert.canada.ca/fr/index.html" + """ + html = """ +

English Message:

+

Test message about {EN_NAME}, last updated {LAST_UPDATED}

+

Link to site

+ +

Message en français:

+

Message test concernant {FR_NAME}, mise à jour la plus récente {LAST_UPDATED}

+

Lien vers le site

+ """ + enSubject = "Keeping the Open Ressource Exchange platform up to date" + + frSubject = "Maintenir la plateforme Échange de Ressources Ouvert à Jour" + + def plainFormat(enName, frName, lastUpdated): + return Templates.plain.format(EN_NAME=enName, LAST_UPDATED=lastUpdated, FR_NAME=frName) + + def htmlFormat(enName, frName, lastUpdated): + return Templates.html.format(EN_NAME=enName, LAST_UPDATED=lastUpdated, FR_NAME=frName) + + def getSubject(): + return Templates.enSubject + " // " + Templates.frSubject \ No newline at end of file From 17ee12fe4ffc007464e9b46c68be04f3ed35f081 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 16 Apr 2020 12:55:19 -0400 Subject: [PATCH 03/12] Templates now contain the right URLs --- assets/py/messageTemplates.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/py/messageTemplates.py b/assets/py/messageTemplates.py index df9336445..6258e6bfb 100644 --- a/assets/py/messageTemplates.py +++ b/assets/py/messageTemplates.py @@ -2,7 +2,7 @@ class Templates: plain = """ English Message: Test message about {EN_NAME}, last updated {LAST_UPDATED} - "https://code.ouvert.canada.ca/en/index.html" + "https://code.open.canada.ca/en/index.html" Message en français: Message test concernant {FR_NAME}, mise à jour la plus récente {LAST_UPDATED} @@ -11,7 +11,7 @@ class Templates: html = """

English Message:

Test message about {EN_NAME}, last updated {LAST_UPDATED}

-

Link to site

+

Link to site

Message en français:

Message test concernant {FR_NAME}, mise à jour la plus récente {LAST_UPDATED}

From 882d09203670e7f937bbf40a0263a423489cb82c Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 17 Apr 2020 15:09:04 -0400 Subject: [PATCH 04/12] Updated templates and the URLs where we get the data --- assets/py/emailUpdateScript.py | 45 +++++++++++------- assets/py/messageTemplates.py | 86 +++++++++++++++++++++++++++++----- 2 files changed, 101 insertions(+), 30 deletions(-) diff --git a/assets/py/emailUpdateScript.py b/assets/py/emailUpdateScript.py index ece106bb5..fd8fe945d 100755 --- a/assets/py/emailUpdateScript.py +++ b/assets/py/emailUpdateScript.py @@ -33,21 +33,20 @@ def sendEmails(emailData): ##Replace with data[0] to actually send the mails to the right place email['to'] = "Simon_moreau@hotmail.ca" email['subject'] = Templates.getSubject() - plainVersion = MIMEText(Templates.plainFormat(data[1], data[2], data[3]), 'plain') + plainVersion = MIMEText(Templates.plainFormat(data[1], data[2], data[3], data[4]), 'plain') plainVersion.add_header("Content-Disposition", "attachment; filename= Plain Text Version.txt") - htmlVersion = MIMEText(Templates.htmlFormat(data[1], data[2], data[3]), 'html') + htmlVersion = MIMEText(Templates.htmlFormat(data[1], data[2], data[3], data[4]), 'html') email.attach(htmlVersion) email.attach(plainVersion) server.send_message(email) del email ##limit how many emails are sent during testing - break server.quit() def checkCodeEmails(): - codeDb = urllib.request.urlopen("https://canada-ca.github.io/ore-ero/code.json") + codeDb = urllib.request.urlopen("https://code.open.canada.ca/code.json") data = json.loads(codeDb.read()) codeData = [] if data is not None: @@ -55,14 +54,16 @@ def checkCodeEmails(): for admin in level.values(): for release in admin["releases"]: if (datetime.strptime(release["date"]["metadataLastUpdated"], '%Y-%m-%d').date() - + timedelta(days=maxDaysNoUpdate) < date.today()): + + timedelta(days=maxDaysNoUpdate) < date.today()): + if "noreply" not in release["contact"]["email"]: codeData.append((release["contact"]["email"], release["name"]["en"], - release["name"]["fr"], release["date"]["metadataLastUpdated"])) + release["name"]["fr"], release["date"]["metadataLastUpdated"], + "code")) return codeData def checkDesignEmails(): - designDb = urllib.request.urlopen("https://canada-ca.github.io/ore-ero/design.json") + designDb = urllib.request.urlopen("https://code.open.canada.ca/design.json") data = json.loads(designDb.read()) designData = [] if data is not None: @@ -70,13 +71,15 @@ def checkDesignEmails(): for administration in project["administrations"]: for use in administration["uses"]: if (datetime.strptime(use["date"]["metadataLastUpdated"], '%Y-%m-%d').date() - + timedelta(days=maxDaysNoUpdate) < date.today()): + + timedelta(days=maxDaysNoUpdate) < date.today()): + if "noreply" not in use["contact"]["email"]: designData.append((use["contact"]["email"], project["name"]["en"], - project["name"]["fr"], use["date"]["metadataLastUpdated"])) + project["name"]["fr"], use["date"]["metadataLastUpdated"], + "design")) return designData def checkSoftwareEmails(): - softwareDb = urllib.request.urlopen("https://canada-ca.github.io/ore-ero/software.json") + softwareDb = urllib.request.urlopen("https://code.open.canada.ca/software.json") data = json.loads(softwareDb.read()) softwareData = [] if data is not None: @@ -84,26 +87,30 @@ def checkSoftwareEmails(): for administration in project["administrations"]: for use in administration["uses"]: if (datetime.strptime(use["date"]["metadataLastUpdated"], '%Y-%m-%d').date() - + timedelta(days=maxDaysNoUpdate) < date.today()): + + timedelta(days=maxDaysNoUpdate) < date.today()): + if "noreply" not in use["contact"]["email"]: softwareData.append((use["contact"]["email"], project["name"]["en"], - project["name"]["fr"], use["date"]["metadataLastUpdated"])) + project["name"]["fr"], use["date"]["metadataLastUpdated"], + "software")) return softwareData def checkStandardEmails(): - standardDb = urllib.request.urlopen("https://canada-ca.github.io/ore-ero/standard.json") + standardDb = urllib.request.urlopen("https://code.open.canada.ca/standard.json") data = json.loads(standardDb.read()) standardData = [] if data is not None: for project in data.values(): for administration in project["administrations"]: if (datetime.strptime(administration["date"]["metadataLastUpdated"], '%Y-%m-%d').date() - + timedelta(days=maxDaysNoUpdate) < date.today()): - standardData.append((administration["contact"]["email"], project["standardAcronym"], - project["standardAcronym"], administration["date"]["metadataLastUpdated"])) + + timedelta(days=maxDaysNoUpdate) < date.today()): + if "noreply" not in administration["contact"]["email"]: + standardData.append((administration["contact"]["email"], project["standardAcronym"], + project["standardAcronym"], administration["date"]["metadataLastUpdated"], + "standard")) return standardData def checkPartnershipEmails(): - partnershipDb = urllib.request.urlopen("https://canada-ca.github.io/ore-ero/partnership.json") + partnershipDb = urllib.request.urlopen("https://code.open.canada.ca/partnership.json") data = json.loads(partnershipDb.read()) partnershipData = [] if data is not None: @@ -112,8 +119,10 @@ def checkPartnershipEmails(): for project in admin["projects"]: if (datetime.strptime(project["date"]["metadataLastUpdated"], '%Y-%m-%d').date() + timedelta(days=maxDaysNoUpdate) < date.today()): + if "noreply" not in project["contact"]["email"]: partnershipData.append((project["contact"]["email"], project["name"]["en"], - project["name"]["fr"], project["date"]["metadataLastUpdated"])) + project["name"]["fr"], project["date"]["metadataLastUpdated"], + "partnership")) return partnershipData def checkOutdatedEmails(): diff --git a/assets/py/messageTemplates.py b/assets/py/messageTemplates.py index 6258e6bfb..a0843ffb2 100644 --- a/assets/py/messageTemplates.py +++ b/assets/py/messageTemplates.py @@ -1,31 +1,93 @@ class Templates: + frTypes = {"code": "code", + "design": "design", + "software": "logiciel", + "standard": "norme", + "partnership": "partenariat"} + + selector = {"code": {"en": "open-source-code-form", "fr": "code-source-ouvert-formulaire"}, + "design": {"en": "open-design-form", "fr": "design-libre-formulaire"}, + "software": {"en": "open-source-software-form", "fr": "logiciel-libre-formulaire"}, + "standard": {"en": "open-standard-form", "fr": "norme-ouverte-formulaire"}, + "partnership": {"en": "partnership-form", "fr": "partenariat-formulaire"}} + plain = """ English Message: - Test message about {EN_NAME}, last updated {LAST_UPDATED} + + Automated message about {EN_NAME} on the ORE platform, last updated {LAST_UPDATED} + You are receiving this message because our information concerning {EN_NAME} has not been updated + in the last 6 months and your email address is currently listed in the contact information for + this {EN_TYPE}. + If you are no longer the contact for {EN_NAME}, + you can use this form "https://code.open.canada.ca/en/{EN_FORM}.html" + to update our platform. + "https://code.open.canada.ca/en/index.html" Message en français: - Message test concernant {FR_NAME}, mise à jour la plus récente {LAST_UPDATED} + Message automatisé concernant {FR_NAME} sur la plateforme Échange de Ressources Ouvert, + mise à jour la plus récente {LAST_UPDATED} + Vous recevez ce message parce que notre information concernant {FR_NAME} n'a pas été mis a jour + dans les 6 derniers mois et votre adresse email est inscrite comme adresse de contact pour + ce {FR_TYPE}. + Si vous n'êtes plus la personne à contacter pour {FR_NAME}, + vous pouvez utiliser ce formulaire "https://code.open.canada.ca/en/{FR_FORM}.html" + pour mettre a jour notre plateforme. + "https://code.ouvert.canada.ca/fr/index.html" """ html = """ +

English Message:

-

Test message about {EN_NAME}, last updated {LAST_UPDATED}

+ Automated message about {EN_NAME} on the Open Ressource Exchange platform + , last updated {LAST_UPDATED} +
+

+ You are receiving this message because our information concerning {EN_NAME} has not been updated + in the last 6 months and your email address is currently listed as the contact address for + this {EN_TYPE}. +

+

+ If you are no longer the contact for {EN_NAME}, + you can use this form + to update our platform. +

+

Link to site

- +

Message en français:

-

Message test concernant {FR_NAME}, mise à jour la plus récente {LAST_UPDATED}

+ Message automatisé concernant {FR_NAME} sur la plateforme Échange de Ressources Ouvert, + mise à jour la plus récente {LAST_UPDATED} +
+

+ Vous recevez ce message parce que notre information concernant {FR_NAME} n'a pas été mis a jour + dans les 6 derniers mois et votre adresse email est inscrite comme adresse de contact pour + ce {FR_TYPE}. +

+

+ Si vous n'êtes plus la personne à contacter pour {FR_NAME}, + vous pouvez utiliser ce formulaire + pour mettre a jour notre plateforme. +

+

Lien vers le site

""" enSubject = "Keeping the Open Ressource Exchange platform up to date" frSubject = "Maintenir la plateforme Échange de Ressources Ouvert à Jour" - def plainFormat(enName, frName, lastUpdated): - return Templates.plain.format(EN_NAME=enName, LAST_UPDATED=lastUpdated, FR_NAME=frName) - - def htmlFormat(enName, frName, lastUpdated): - return Templates.html.format(EN_NAME=enName, LAST_UPDATED=lastUpdated, FR_NAME=frName) - + @staticmethod + def plainFormat(enName, frName, lastUpdated, category): + return Templates.plain.format(EN_NAME=enName, FR_NAME=frName, LAST_UPDATED=lastUpdated, + EN_TYPE=category, FR_TYPE=Templates.frTypes[category], + EN_FORM=Templates.selector[category]["en"], FR_FORM=Templates.selector[category]["fr"]) + + @staticmethod + def htmlFormat(enName, frName, lastUpdated, category): + return Templates.html.format(EN_NAME=enName, FR_NAME=frName, LAST_UPDATED=lastUpdated, + EN_TYPE=category, FR_TYPE=Templates.frTypes[category], + EN_FORM=Templates.selector[category]["en"], FR_FORM=Templates.selector[category]["fr"]) + + @staticmethod def getSubject(): - return Templates.enSubject + " // " + Templates.frSubject \ No newline at end of file + return Templates.enSubject + " // " + Templates.frSubject From c473e5e7c89e865ac968b27d60d80ac1adc9240c Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 20 Apr 2020 09:23:52 -0400 Subject: [PATCH 05/12] Preparation for implementation --- assets/py/emailUpdateScript.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/assets/py/emailUpdateScript.py b/assets/py/emailUpdateScript.py index fd8fe945d..3d3d4877e 100755 --- a/assets/py/emailUpdateScript.py +++ b/assets/py/emailUpdateScript.py @@ -11,7 +11,8 @@ ############################################################################### ##Replace with whichever gmail will be used to send the messages, can use this one for tests sender = "scheduledupdatescripttester@gmail.com" -#App password for the gmail +#App password for the gmail, for implementation should be kept in a file that isn't included in the git +#repo, or required as a parameter when you call the script password = "koppttrkbyodglmc" #Maximum amount of days since last update, half a year default maxDaysNoUpdate = 182 @@ -30,8 +31,10 @@ def sendEmails(emailData): for data in emailData: email = MIMEMultipart() email['from'] = sender - ##Replace with data[0] to actually send the mails to the right place - email['to'] = "Simon_moreau@hotmail.ca" + ##Implementation + #email['to'] = data[0] + ##Any address to test the script + #email['to'] = email['subject'] = Templates.getSubject() plainVersion = MIMEText(Templates.plainFormat(data[1], data[2], data[3], data[4]), 'plain') plainVersion.add_header("Content-Disposition", @@ -42,6 +45,7 @@ def sendEmails(emailData): server.send_message(email) del email ##limit how many emails are sent during testing + #break server.quit() From 6f1e6dbad214dd976829a12ba4e1e5299b073278 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 21 Apr 2020 11:18:07 -0400 Subject: [PATCH 06/12] Update using workflow and more setup for more secure use --- .github/workflows/updateContacts.yml | 13 ++++++++ assets/py/emailInfo.py | 5 ++++ assets/py/emailUpdateScript.py | 45 +++++++--------------------- assets/py/messageTemplates.py | 27 ++++++++--------- 4 files changed, 41 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/updateContacts.yml create mode 100644 assets/py/emailInfo.py diff --git a/.github/workflows/updateContacts.yml b/.github/workflows/updateContacts.yml new file mode 100644 index 000000000..6427c7e0a --- /dev/null +++ b/.github/workflows/updateContacts.yml @@ -0,0 +1,13 @@ +name: Contact Email Update + +on: + schedule: + - cron: '0 0 31 1,7 *' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Contact Email Update + run: ./assets/py/emailUpdateScript.py \ No newline at end of file diff --git a/assets/py/emailInfo.py b/assets/py/emailInfo.py new file mode 100644 index 000000000..e7498fefb --- /dev/null +++ b/assets/py/emailInfo.py @@ -0,0 +1,5 @@ +#Fill with your own set and .gitignore this file +emailInfo = { + "email":"", + "password":"" +} \ No newline at end of file diff --git a/assets/py/emailUpdateScript.py b/assets/py/emailUpdateScript.py index 3d3d4877e..fded49442 100755 --- a/assets/py/emailUpdateScript.py +++ b/assets/py/emailUpdateScript.py @@ -1,40 +1,26 @@ #!/usr/bin/env python3 # This Python file uses the following encoding: utf-8 -import json, schedule, smtplib, time +import json, smtplib import urllib.request from datetime import date, timedelta, datetime from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from messageTemplates import Templates +from emailInfo import emailInfo ############################################################################### ###From ore-ero folder, run with ./assets/py/emailUpdateScript.py ### ############################################################################### -##Replace with whichever gmail will be used to send the messages, can use this one for tests -sender = "scheduledupdatescripttester@gmail.com" -#App password for the gmail, for implementation should be kept in a file that isn't included in the git -#repo, or required as a parameter when you call the script -password = "koppttrkbyodglmc" #Maximum amount of days since last update, half a year default maxDaysNoUpdate = 182 -##For the scheduler -#weeksToExec if we want to make sure it's done at a specific day of the week, half a year default -weeksToExec = 26 -#daysToExec can be changed to whatever number as long as it's long enough not to spam contacts, half a year default -daysToExec = 182 -#execTime doesn't really matter considering speed of execution, default at midnight -#To test it change this to a time at least 1 minute after your current time -execTime = "00:00" def sendEmails(emailData): server = smtplib.SMTP_SSL(host='smtp.gmail.com') - server.login(sender, password) + server.login(emailInfo["email"], emailInfo["password"]) for data in emailData: email = MIMEMultipart() - email['from'] = sender - ##Implementation - #email['to'] = data[0] - ##Any address to test the script - #email['to'] = + email['from'] = emailInfo["email"] + #Replace with any address to test the script + email['to'] = data[0] email['subject'] = Templates.getSubject() plainVersion = MIMEText(Templates.plainFormat(data[1], data[2], data[3], data[4]), 'plain') plainVersion.add_header("Content-Disposition", @@ -44,8 +30,6 @@ def sendEmails(emailData): email.attach(plainVersion) server.send_message(email) del email - ##limit how many emails are sent during testing - #break server.quit() @@ -129,18 +113,9 @@ def checkPartnershipEmails(): "partnership")) return partnershipData -def checkOutdatedEmails(): - print("Started task at: " + datetime.now().isoformat(' ', 'seconds')) - sendEmails(checkCodeEmails() + checkDesignEmails() + checkSoftwareEmails() - + checkStandardEmails() + checkPartnershipEmails()) - print("Finished task at: " + datetime.now().isoformat(' ', 'seconds')) -#Implementation -##schedule.every(weeksToExec).weeks.at(execTime).do(checkOutdatedEmails) -##schedule.every(daysToExec).days.at(execTime).do(checkOutdatedEmails) -#For testing -schedule.every().day.at(execTime).do(checkOutdatedEmails) +print("Started task at: " + datetime.now().isoformat(' ', 'seconds')) +sendEmails(checkCodeEmails() + checkDesignEmails() + checkSoftwareEmails() + + checkStandardEmails() + checkPartnershipEmails()) +print("Finished task at: " + datetime.now().isoformat(' ', 'seconds')) -while True: - schedule.run_pending() - time.sleep(1) \ No newline at end of file diff --git a/assets/py/messageTemplates.py b/assets/py/messageTemplates.py index a0843ffb2..cfd4aceab 100644 --- a/assets/py/messageTemplates.py +++ b/assets/py/messageTemplates.py @@ -14,25 +14,24 @@ class Templates: plain = """ English Message: - Automated message about {EN_NAME} on the ORE platform, last updated {LAST_UPDATED} - You are receiving this message because our information concerning {EN_NAME} has not been updated - in the last 6 months and your email address is currently listed in the contact information for - this {EN_TYPE}. - If you are no longer the contact for {EN_NAME}, - you can use this form "https://code.open.canada.ca/en/{EN_FORM}.html" - to update our platform. + Automated message about {EN_NAME} on the ORE platform, last updated {LAST_UPDATED}. + You are receiving this message because our information concerning {EN_NAME} has not been + updated in the last 6 months and your email address is currently listed as the contact + information for this {EN_TYPE}. + If you are no longer the contact for {EN_NAME}, you can use this form + "https://code.open.canada.ca/en/{EN_FORM}.html" to update our platform. "https://code.open.canada.ca/en/index.html" Message en français: Message automatisé concernant {FR_NAME} sur la plateforme Échange de Ressources Ouvert, - mise à jour la plus récente {LAST_UPDATED} - Vous recevez ce message parce que notre information concernant {FR_NAME} n'a pas été mis a jour - dans les 6 derniers mois et votre adresse email est inscrite comme adresse de contact pour - ce {FR_TYPE}. - Si vous n'êtes plus la personne à contacter pour {FR_NAME}, - vous pouvez utiliser ce formulaire "https://code.open.canada.ca/en/{FR_FORM}.html" - pour mettre a jour notre plateforme. + mise à jour la plus récente {LAST_UPDATED} + Vous recevez ce message parce que notre information concernant {FR_NAME} n'a pas été + mis a jour dans les 6 derniers mois et votre adresse email est inscrite comme + adresse de contact pour ce {FR_TYPE}. + Si vous n'êtes plus la personne à contacter pour {FR_NAME}, vous pouvez utiliser + ce formulaire "https://code.open.canada.ca/en/{FR_FORM}.html" pour mettre + a jour notre plateforme. "https://code.ouvert.canada.ca/fr/index.html" """ From b0709c2aa303a1200e83ee66be6d071765179dcc Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 21 Apr 2020 11:22:40 -0400 Subject: [PATCH 07/12] Minor update --- assets/py/emailInfo.py | 4 ++-- assets/py/emailUpdateScript.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/py/emailInfo.py b/assets/py/emailInfo.py index e7498fefb..e56079b89 100644 --- a/assets/py/emailInfo.py +++ b/assets/py/emailInfo.py @@ -1,5 +1,5 @@ -#Fill with your own set and .gitignore this file +#Fill with your own set and .gitignore this file, recommend using an app password emailInfo = { - "email":"", + "address":"", "password":"" } \ No newline at end of file diff --git a/assets/py/emailUpdateScript.py b/assets/py/emailUpdateScript.py index fded49442..fecd3ba7d 100755 --- a/assets/py/emailUpdateScript.py +++ b/assets/py/emailUpdateScript.py @@ -15,10 +15,10 @@ def sendEmails(emailData): server = smtplib.SMTP_SSL(host='smtp.gmail.com') - server.login(emailInfo["email"], emailInfo["password"]) + server.login(emailInfo["address"], emailInfo["password"]) for data in emailData: email = MIMEMultipart() - email['from'] = emailInfo["email"] + email['from'] = emailInfo["address"] #Replace with any address to test the script email['to'] = data[0] email['subject'] = Templates.getSubject() From a8c5236852e8a1408782e45ce12e15dff2e11492 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 22 Apr 2020 10:01:58 -0400 Subject: [PATCH 08/12] Get password through env variable defined from git secret --- .github/workflows/updateContacts.yml | 4 ++++ assets/py/emailInfo.py | 5 ----- assets/py/emailUpdateScript.py | 8 ++++---- 3 files changed, 8 insertions(+), 9 deletions(-) delete mode 100644 assets/py/emailInfo.py diff --git a/.github/workflows/updateContacts.yml b/.github/workflows/updateContacts.yml index 6427c7e0a..aad8db142 100644 --- a/.github/workflows/updateContacts.yml +++ b/.github/workflows/updateContacts.yml @@ -10,4 +10,8 @@ jobs: steps: - name: Contact Email Update + env: + ##Insert address here + GMAIL_ADDRESS: + GMAIL_APP_PASSWORD: ${{ secrets.GmailAppPassword }} run: ./assets/py/emailUpdateScript.py \ No newline at end of file diff --git a/assets/py/emailInfo.py b/assets/py/emailInfo.py deleted file mode 100644 index e56079b89..000000000 --- a/assets/py/emailInfo.py +++ /dev/null @@ -1,5 +0,0 @@ -#Fill with your own set and .gitignore this file, recommend using an app password -emailInfo = { - "address":"", - "password":"" -} \ No newline at end of file diff --git a/assets/py/emailUpdateScript.py b/assets/py/emailUpdateScript.py index fecd3ba7d..0376af74b 100755 --- a/assets/py/emailUpdateScript.py +++ b/assets/py/emailUpdateScript.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 # This Python file uses the following encoding: utf-8 -import json, smtplib +import json, os, smtplib import urllib.request from datetime import date, timedelta, datetime from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from messageTemplates import Templates -from emailInfo import emailInfo ############################################################################### ###From ore-ero folder, run with ./assets/py/emailUpdateScript.py ### ############################################################################### @@ -14,11 +13,12 @@ maxDaysNoUpdate = 182 def sendEmails(emailData): + address = os.getenv("GMAIL_ADDRESS") server = smtplib.SMTP_SSL(host='smtp.gmail.com') - server.login(emailInfo["address"], emailInfo["password"]) + server.login(address, os.getenv("GMAIL_APP_PASSWORD")) for data in emailData: email = MIMEMultipart() - email['from'] = emailInfo["address"] + email['from'] = address #Replace with any address to test the script email['to'] = data[0] email['subject'] = Templates.getSubject() From 7d4f122cb9f014f49086b3ee85f7a4325b1bfcf2 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 22 Apr 2020 10:13:00 -0400 Subject: [PATCH 09/12] Missing newline at end of file --- .github/workflows/updateContacts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/updateContacts.yml b/.github/workflows/updateContacts.yml index aad8db142..7d1b49e55 100644 --- a/.github/workflows/updateContacts.yml +++ b/.github/workflows/updateContacts.yml @@ -14,4 +14,4 @@ jobs: ##Insert address here GMAIL_ADDRESS: GMAIL_APP_PASSWORD: ${{ secrets.GmailAppPassword }} - run: ./assets/py/emailUpdateScript.py \ No newline at end of file + run: ./assets/py/emailUpdateScript.py From 255ff59c1de99eddde5a3b46a3fa5f347b2788a6 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 23 Apr 2020 10:56:49 -0400 Subject: [PATCH 10/12] Address as secret rather than empty field to be filled later --- .github/workflows/updateContacts.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/updateContacts.yml b/.github/workflows/updateContacts.yml index 7d1b49e55..94aacbf68 100644 --- a/.github/workflows/updateContacts.yml +++ b/.github/workflows/updateContacts.yml @@ -11,7 +11,6 @@ jobs: steps: - name: Contact Email Update env: - ##Insert address here - GMAIL_ADDRESS: + GMAIL_ADDRESS: ${{ secrets.GmailAddress }} GMAIL_APP_PASSWORD: ${{ secrets.GmailAppPassword }} run: ./assets/py/emailUpdateScript.py From 47b4fef0012a3fb73c5d247f6e90cd5e75fa8096 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 27 Apr 2020 08:51:54 -0400 Subject: [PATCH 11/12] Updated so it gets master before trying to run the script --- .github/workflows/updateContacts.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/updateContacts.yml b/.github/workflows/updateContacts.yml index 94aacbf68..c09a747f6 100644 --- a/.github/workflows/updateContacts.yml +++ b/.github/workflows/updateContacts.yml @@ -9,6 +9,9 @@ jobs: runs-on: ubuntu-latest steps: + - name: Checkout Main repo + uses: actions/checkout@v2 + - name: Contact Email Update env: GMAIL_ADDRESS: ${{ secrets.GmailAddress }} From 54f87c90ce2dc8dbd824d3d1a3690506629ffc65 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 30 Apr 2020 12:51:47 -0400 Subject: [PATCH 12/12] Setup to use Notify API instead of gmail --- .github/workflows/updateContacts.yml | 4 +- assets/py/emailUpdateScript.py | 43 +++++++++--------- assets/py/messageTemplates.py | 68 ++-------------------------- 3 files changed, 27 insertions(+), 88 deletions(-) diff --git a/.github/workflows/updateContacts.yml b/.github/workflows/updateContacts.yml index c09a747f6..038320b05 100644 --- a/.github/workflows/updateContacts.yml +++ b/.github/workflows/updateContacts.yml @@ -14,6 +14,6 @@ jobs: - name: Contact Email Update env: - GMAIL_ADDRESS: ${{ secrets.GmailAddress }} - GMAIL_APP_PASSWORD: ${{ secrets.GmailAppPassword }} + API_KEY: ${{ secrets.NotifyAPIKey }} + TEMPLATE_ID: "" run: ./assets/py/emailUpdateScript.py diff --git a/assets/py/emailUpdateScript.py b/assets/py/emailUpdateScript.py index 0376af74b..cf01bdba6 100755 --- a/assets/py/emailUpdateScript.py +++ b/assets/py/emailUpdateScript.py @@ -1,36 +1,37 @@ #!/usr/bin/env python3 # This Python file uses the following encoding: utf-8 -import json, os, smtplib +import json, os import urllib.request from datetime import date, timedelta, datetime -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText -from messageTemplates import Templates +from notifications_python_client.notifications import NotificationsAPIClient ############################################################################### ###From ore-ero folder, run with ./assets/py/emailUpdateScript.py ### ############################################################################### #Maximum amount of days since last update, half a year default maxDaysNoUpdate = 182 +frTypes = {"code": "code", + "design": "design", + "software": "logiciel", + "standard": "norme", + "partnership": "partenariat"} + +formName = {"code": {"en": "open-source-code-form", "fr": "code-source-ouvert-formulaire"}, + "design": {"en": "open-design-form", "fr": "design-libre-formulaire"}, + "software": {"en": "open-source-software-form", "fr": "logiciel-libre-formulaire"}, + "standard": {"en": "open-standard-form", "fr": "norme-ouverte-formulaire"}, + "partnership": {"en": "partnership-form", "fr": "partenariat-formulaire"}} + + def sendEmails(emailData): - address = os.getenv("GMAIL_ADDRESS") - server = smtplib.SMTP_SSL(host='smtp.gmail.com') - server.login(address, os.getenv("GMAIL_APP_PASSWORD")) + client = NotificationsAPIClient(os.getenv("API_KEY"), "https://api.notification.alpha.canada.ca") for data in emailData: - email = MIMEMultipart() - email['from'] = address - #Replace with any address to test the script - email['to'] = data[0] - email['subject'] = Templates.getSubject() - plainVersion = MIMEText(Templates.plainFormat(data[1], data[2], data[3], data[4]), 'plain') - plainVersion.add_header("Content-Disposition", - "attachment; filename= Plain Text Version.txt") - htmlVersion = MIMEText(Templates.htmlFormat(data[1], data[2], data[3], data[4]), 'html') - email.attach(htmlVersion) - email.attach(plainVersion) - server.send_message(email) - del email - server.quit() + #Replace data[0] with any address to test the script + client.send_notification( + data[0], os.getenv("TEMPLATE_ID"), + {'EN_NAME': data[1], 'FR_NAME': data[2], 'LAST_UPDATED': data[3], + 'EN_TYPE': data[4], 'FR_TYPE': frTypes[data[4]], + 'EN_FORM': formName[data[4]]["en"], 'FR_FORM': formName[data[4]]["fr"]}) def checkCodeEmails(): diff --git a/assets/py/messageTemplates.py b/assets/py/messageTemplates.py index cfd4aceab..ea49f64b5 100644 --- a/assets/py/messageTemplates.py +++ b/assets/py/messageTemplates.py @@ -1,16 +1,6 @@ class Templates: - frTypes = {"code": "code", - "design": "design", - "software": "logiciel", - "standard": "norme", - "partnership": "partenariat"} - - selector = {"code": {"en": "open-source-code-form", "fr": "code-source-ouvert-formulaire"}, - "design": {"en": "open-design-form", "fr": "design-libre-formulaire"}, - "software": {"en": "open-source-software-form", "fr": "logiciel-libre-formulaire"}, - "standard": {"en": "open-standard-form", "fr": "norme-ouverte-formulaire"}, - "partnership": {"en": "partnership-form", "fr": "partenariat-formulaire"}} - + ##This file can be erased once the template is setup on the Notify API + ##To have this template on the Notify API, replace {} with (()) plain = """ English Message: @@ -24,6 +14,7 @@ class Templates: "https://code.open.canada.ca/en/index.html" Message en français: + Message automatisé concernant {FR_NAME} sur la plateforme Échange de Ressources Ouvert, mise à jour la plus récente {LAST_UPDATED} Vous recevez ce message parce que notre information concernant {FR_NAME} n'a pas été @@ -35,58 +26,5 @@ class Templates: "https://code.ouvert.canada.ca/fr/index.html" """ - html = """ - -

English Message:

- Automated message about {EN_NAME} on the Open Ressource Exchange platform - , last updated {LAST_UPDATED} -
-

- You are receiving this message because our information concerning {EN_NAME} has not been updated - in the last 6 months and your email address is currently listed as the contact address for - this {EN_TYPE}. -

-

- If you are no longer the contact for {EN_NAME}, - you can use this form - to update our platform. -

-
-

Link to site

- -

Message en français:

- Message automatisé concernant {FR_NAME} sur la plateforme Échange de Ressources Ouvert, - mise à jour la plus récente {LAST_UPDATED} -
-

- Vous recevez ce message parce que notre information concernant {FR_NAME} n'a pas été mis a jour - dans les 6 derniers mois et votre adresse email est inscrite comme adresse de contact pour - ce {FR_TYPE}. -

-

- Si vous n'êtes plus la personne à contacter pour {FR_NAME}, - vous pouvez utiliser ce formulaire - pour mettre a jour notre plateforme. -

-
-

Lien vers le site

- """ - enSubject = "Keeping the Open Ressource Exchange platform up to date" - - frSubject = "Maintenir la plateforme Échange de Ressources Ouvert à Jour" - - @staticmethod - def plainFormat(enName, frName, lastUpdated, category): - return Templates.plain.format(EN_NAME=enName, FR_NAME=frName, LAST_UPDATED=lastUpdated, - EN_TYPE=category, FR_TYPE=Templates.frTypes[category], - EN_FORM=Templates.selector[category]["en"], FR_FORM=Templates.selector[category]["fr"]) - @staticmethod - def htmlFormat(enName, frName, lastUpdated, category): - return Templates.html.format(EN_NAME=enName, FR_NAME=frName, LAST_UPDATED=lastUpdated, - EN_TYPE=category, FR_TYPE=Templates.frTypes[category], - EN_FORM=Templates.selector[category]["en"], FR_FORM=Templates.selector[category]["fr"]) - @staticmethod - def getSubject(): - return Templates.enSubject + " // " + Templates.frSubject