diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b19b14f --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +EMAIL_SERVER=dein.server.com +EMAIL_ADDRESS=email@server.com +EMAIL_USERNAME=username +EMAIL_PASSWORD=password +BOT_API=https://deine.nextcloud.de/ocs/v1.php/apps/spreed/api/v1/chat/ +BOT_USERNAME=botusername +BOT_PASSWORD=botpassword +BOT_ROOMTOKEN=28fu49fg \ No newline at end of file diff --git a/.gitignore b/.gitignore index b92898f..a6b3ac4 100644 --- a/.gitignore +++ b/.gitignore @@ -153,3 +153,4 @@ cython_debug/ #.idea/ contacts.json +executionDate.json \ No newline at end of file diff --git a/README.md b/README.md index 48a4e1c..3577f93 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,48 @@ -# email_util +# Maily +## Inhaltsverzeichnis +- [Einleitung](#einleitung) +- [Installation](#installation) +- [Code Rundown](#codeRundown) + +--- + +## Einleitung +Maily wurde entwickelt um auf unseren Email Postfächern automatisch zu antworten und uns darüber zu informieren wer uns geschrieben hat. + +Wichtig war auch das Maily neuen Kontakten nur ein mal schreibt. Beim eingebauten Autoresponder unseres Email-Servers konnte man nur eine 'Sperrstunde' einstellen in der automatisch geantwortet wird. Auf alles. Und jeden. Immer. Sogar in einem aktiven Email-Verlauf hätte dieser Autoresponder einfach geantwortet. + +Irgendwie sollte Maily uns wissen lassen wann und was sie macht damit wir im Bilde sind. Hier habe ich mich dazu entschieden dass Maily ein kleines 'Chatbot' modul bekommt und uns in Nextcloud-Talk schreibt. Der Vorteil dabei ist das man Maily direkt in den richtigen Channel schreiben lassen kann, so das alle Leute informiert werden die mit dem Postfach arbeiten. + +Keiner hat zeit alle 2 Tage Logdaten auf einem Server nachzuschauen. +## Installation +Die installation einer Dev-Umgebung ist relativ straight forward, allerdings sollte beachtet werden das man Maily auf einer `UNIX` Maschine entwickeln sollte. Das interface zwischen Maily und Nextcloud wird mit einem `.sh` Skript ausgeführt der auf Windows nur schwer zum Laufen gebracht werden kann. +### Step 1 +Als erstes muss das Repository heruntergeladen werden +``` +$ git clone https://git.sommerschein.de/Sommerschein/email_util.git +``` +### Step 2 +Es muss eine `Virtuelle Umgebung` erstellt und die benötigten Module installiert werden. +``` +$ python3 -m venv .venv +``` +Mit diesem befehl wird eine sperierte version von `Python` im `Projekt` abgelegt, so dass keine Probleme mit neuen Versionen in den Modulen auftreten. +Dann noch aktivieren: +``` +$ source .venv/bin/activate +``` +Als nächstes muss die `requirements.txt` Datei installiert werden, damit wir alle Module bereit haben. +``` +$ pip install -r requirements.txt +``` +### Step 3 +Die `.env.example` Datei in `.env` umbenennen und anpassen. +Das `BOT_PASSWORD` wird in der Cloud als `APP-Passwort` eingerichtet. So muss nicht der ganze User übergeben werden. Ausserdem kann der zugriff auf die Dateien begrenzt werden. +Der `BOT_ROOMTOKEN` ist der `Token` zu dem Raum in dem Maily schreibt. Man findet ihn am Ende der `URL` wen man den gewünschten Raum im Browser öffnet. +Die `BOT_API` ist immer gleich, hier muss nur die `URL` am anfang auf die gewünschte Cloud angepasst werden +Die `EMAIL` Variablen sind im grunde standart Login daten zu einem Email Server. +### Step 4 +Im grunde sind wir fertig, ab jetzt kann an Maily entwickelt werden. Wenn man den `autoResponder.py` skript ausführt wird Maily angewendet und versucht seinen Job zu machen. +## Code Rundown +Wird angepasst wen 1.0 fertig ist. -Kleies Utility programm für unsere Email verwaltung \ No newline at end of file diff --git a/autoResponder.py b/autoResponder.py index 037bba5..c0a5b94 100644 --- a/autoResponder.py +++ b/autoResponder.py @@ -1,29 +1,43 @@ -import codecs -from mail_util import checkFor_unkownContacts, sendEmails, jsonAddContacts +""" +This file will run Maily. +It will try to scan for new Contacts and if it found any it will write them an email. +Also Maily will inform us about any written emails via Nextcloud-Talk +""" +import os +from mail_util import checkFor_unkownContacts, sendEmails, jsonAddContacts, lastExecutionDate_Load, jsonCreateContacts, lastExecutionDate_Safe from chatbot import sendMessage from config import today, yesterday +#! This function reads the response text-file def getResponseText(): with open('responses/bookingResponse.txt', 'r') as file: data = file.read() return data - -contacts = checkFor_unkownContacts() -#contacts.append('e.neug@icloud.com') responseText = getResponseText() -print(responseText) -sendEmails(responseText, contacts) -jsonAddContacts(contacts) +lastExecutionDate = lastExecutionDate_Load() + chatTextIntro = f"Hey! ich wurde Heute ({today}) ausgeführt und suche für Gestern({yesterday}) nach neuen Kontakten auf der booking@" -chatTextAmmount = f"Ich habe {len(contacts)} neue Kontakte gefunden und automatisch geschrieben" chatTextNone = f"Ich habe keine neuen Kontakte gefunden" +chatTextNew = f"Hey! Ich wurde Heute ({today}) ausgeführt. Ich suche nach nicht beantworteten Emails, seit meiner letzten ausführung ({lastExecutionDate})" + +if os.path.isfile('./contacts.json'): + contacts = checkFor_unkownContacts() + sendMessage(chatTextNew) + if len(contacts) > 0: + chatTextAmmount = f"Ich habe {len(contacts)} unbeantwortete Emails gefunden und automatisch geantwortet" + sendMessage(chatTextAmmount) + for c in contacts: + sendMessage(c) + + sendEmails(responseText, contacts) + jsonAddContacts(contacts) + lastExecutionDate_Safe(str(today)) -sendMessage(chatTextIntro) -if len(contacts) > 0: - sendMessage(chatTextAmmount) - for c in contacts: - sendMessage(c) + else: + sendMessage(chatTextNone) else: - sendMessage(chatTextNone) + jsonCreateContacts() + print("I created the Contacts, run me again.") + diff --git a/chatbot.py b/chatbot.py index 2322a24..f225f51 100644 --- a/chatbot.py +++ b/chatbot.py @@ -1,5 +1,10 @@ +""" +This File will use the 'sender.sh' shell script to send messages to Nextcloud-Talk +""" + import subprocess +from config import botAPI, botUsername, botPassword, botRoomToken def sendMessage(message): - subprocess.call(['sh','./sender2.sh', message]) + subprocess.call(['sh','./sender.sh', botAPI, botUsername, botPassword, botRoomToken, message]) diff --git a/config.py b/config.py index 23e7396..c1cccfa 100644 --- a/config.py +++ b/config.py @@ -1,20 +1,34 @@ +""" +This file holds all the important variables used accross this programm +""" + import os from dotenv import load_dotenv from datetime import date, timedelta load_dotenv() -pw = os.getenv('PASSWORD') -server ="w00e1ce2.kasserver.com" -username ="booking@sommerschein.de" +# Setup Email Credentials +server = os.getenv('EMAIL_SERVER') +address = os.getenv('EMAIL_ADDRESS') +username = os.getenv('EMAIL_USERNAME') +password = os.getenv('EMAIL_PASSWORD') + +# Setup Chatbot Credentials +botAPI = os.getenv('BOT_API') +botUsername = os.getenv('BOT_USERNAME') +botPassword = os.getenv('BOT_PASSWORD') +botRoomToken = os.getenv('BOT_ROOMTOKEN') +# Setup important Times and Formatting them today = date.today() yesterday = today - timedelta(days=1) dateStringToday = today.strftime("%d-%b-%Y") dateStringYesterday = yesterday.strftime("%d-%b-%Y") -searchUnawnsered = "UNANSWERED SENTSINCE 01-Sep-2022" +# Setup Search parameter +searchUnawnsered = "UNANSWERED SENTSINCE " searchAll = 'ALL' -searchSentsince = 'SENTSINCE 01-Sep-2022' +searchSentsince = 'SENTSINCE ' searchToday = 'SENTON ' + dateStringToday searchYesterday = 'SENTON ' + dateStringYesterday \ No newline at end of file diff --git a/mail_util.py b/mail_util.py index 4544ccd..5f7b424 100644 --- a/mail_util.py +++ b/mail_util.py @@ -1,42 +1,51 @@ -import imaplib, email, os, json -from config import server, username, pw, searchAll, searchYesterday +""" +This File handles all the email and contact shenanigans, +its kind of convoluted at the Moment and needs a bit of cleanup. +I tried to keep everything in functions so it stays readable. +Some handle emails and the smtp & imap server-side, +the others Json stuff to extract email addresses and save them as contacts for future use. +""" +import imaplib, email, os, json from smtplib import SMTP_SSL, SMTP_SSL_PORT - - - +from config import server, username, password, address, searchAll, searchYesterday, searchUnawnsered, today, searchSentsince +from datetime import datetime +#! This function takes a list of Email-Addresses and sends them emails def sendEmails(text, reciever): + # Credential Setup SMTP_HOST = server - SMTP_USER = username - SMTP_PASS = pw - - # Craft the email by hand - from_email = 'Maily-Sommerschein ' # or simply the email address - to_emails = reciever - body = text - headers = f"From: {from_email}\r\n" - headers += f"To: {', '.join(to_emails)}\r\n" - headers += f"Subject: Autoresponse\r\n" - email_message = headers + "\r\n" + body # Blank line needed between headers and body - - # Connect, authenticate, and send mail + SMTP_USER = address + SMTP_PASS = password + + # Connect to Server smtp_server = SMTP_SSL(SMTP_HOST, port=SMTP_SSL_PORT) smtp_server.set_debuglevel(1) # Show SMTP server interactions smtp_server.login(SMTP_USER, SMTP_PASS) - smtp_server.sendmail(from_email, to_emails, email_message) - - # Disconnect + + for r in reciever: + # Craft the email + from_email = f'{username} <{address}>' + headers = f"From: {from_email}\r\n" + headers += f"To: {', '.join(r)}\r\n" + headers += f"Subject: Autoresponse\r\n" + email_message = headers + "\r\n" + text + # Send mail + smtp_server.sendmail(from_email, r, email_message) + + # Disconnect from Server smtp_server.quit() +#! This function loads all known contacts from the Json file def loadKnownContacts(): f = open('contacts.json') contacts = json.load(f) return contacts +#! This function returns all Contacts from the Inbox def getContacts(search): # Connect to inbox imap_server = imaplib.IMAP4_SSL(host=server) - imap_server.login(username, pw) + imap_server.login(address, password) imap_server.select() # Default is `INBOX` contacts = [] @@ -58,17 +67,14 @@ def getContacts(search): contact = c.split('<') if len(contact) == 2: contact = contact[1].split('>') - #contact = contact[0].split('(') newContacts.append(contact[0]) - #print(contact[0]) else: contact = contact[0].split('(') - #print(contact[0]) newContacts.append(contact[0]) - return newContacts +#! This function adds new contacts to the json file for later use def jsonAddContacts(contacts): knowContacts = loadKnownContacts() for c in contacts: @@ -81,10 +87,11 @@ def jsonAddContacts(contacts): with open('contacts.json', 'w', encoding='utf-8') as f: json.dump(knowContacts, f, ensure_ascii=False, indent=4) +#! This function creates the initial Json file with all already known contacts from the inbox def jsonCreateContacts(): # Connect to inbox imap_server = imaplib.IMAP4_SSL(host=server) - imap_server.login(username, pw) + imap_server.login(address, password) imap_server.select() # Default is `INBOX` contacts = [] @@ -101,43 +108,45 @@ def jsonCreateContacts(): contacts.append(contact) newContacts = [] - rawContacts = [] for c in contacts: - rawContacts.append(c) contact = c.split('<') if len(contact) == 2: contact = contact[1].split('>') - #contact = contact[0].split('(') newContacts.append(contact[0]) - #print(contact[0]) else: contact = contact[0].split('(') - #print(contact[0]) newContacts.append(contact[0]) if os.path.isfile('./contacts.json'): - print('Deleting Contacts') os.remove('contacts.json') - - if os.path.isfile('./rawContacts.json'): - print('Deleting Contacts') - os.remove('rawContacts.json') - with open('contacts.json', 'w', encoding='utf-8') as f: json.dump(newContacts, f, ensure_ascii=False, indent=4) - with open('rawContacts.json', 'w', encoding='utf-8') as f: - json.dump(rawContacts, f, ensure_ascii=False, indent=4) - +#! This function will safe the last execution date in a json-file +def lastExecutionDate_Safe (executionDate): + if os.path.isfile('./executionDate.json'): + os.remove('executionDate.json') + + with open('executionDate.json', 'w', encoding='utf-8') as f: + json.dump(executionDate, f, ensure_ascii=False, indent=4) + +#! This function will load the last execution date +def lastExecutionDate_Load(): + f = open('executionDate.json') + date = json.load(f) + return date + +#! This function returns all unkown contacts def checkFor_unkownContacts(): - contacts = getContacts(searchYesterday) + lastExecution = lastExecutionDate_Load() + date = datetime.strptime(lastExecution, "%Y-%m-%d") + contacts = getContacts(searchUnawnsered + date.strftime("%d-%b-%Y")) knownContacts = loadKnownContacts() unknownContacts = [] for c in contacts: if c not in knownContacts: - # TODO: send Email - #print(c) unknownContacts.append(c) return unknownContacts + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9995d1a --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +python-dotenv==0.21.0 diff --git a/sender.sh b/sender.sh old mode 100755 new mode 100644 index 21dbe4e..135c516 --- a/sender.sh +++ b/sender.sh @@ -1,7 +1,9 @@ #!/bin/bash -API=https://cloud1.sommerschein.de/ocs/v1.php/apps/spreed/api/v1/chat/xzntzmuh -LOGIN="Maily:Gtjik-qP48b-y62bC-r8yos-qJT9Y" -MESSAGE=$1 +API=$1 +USERNAME=$2 +PASSWORD=$3 +TOKEN=$4 +MESSAGE=$5 -curl -d '{"token":"xzntzmuh", "message":"'"$MESSAGE"'"}' -H "Content-Type: application/json" -H "Accept:application/json" -H "OCS-APIRequest:true" -u "$LOGIN" $API 2> /dev/null | > /dev/null \ No newline at end of file +curl -d '{"token":"'"$TOKEN"'", "message":"'"$MESSAGE"'"}' -H "Content-Type: application/json" -H "Accept:application/json" -H "OCS-APIRequest:true" -u "$USERNAME:$PASSWORD" $API$TOKEN 2> /dev/null | > /dev/null \ No newline at end of file diff --git a/sender2.sh b/sender2.sh deleted file mode 100644 index 5d6a9bd..0000000 --- a/sender2.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -API=https://cloud1.sommerschein.de/ocs/v1.php/apps/spreed/api/v1/chat/onxr79pz -LOGIN="Maily:Gtjik-qP48b-y62bC-r8yos-qJT9Y" -MESSAGE=$1 - -curl -d '{"token":"onxr79pz", "message":"'"$MESSAGE"'"}' -H "Content-Type: application/json" -H "Accept:application/json" -H "OCS-APIRequest:true" -u "$LOGIN" $API 2> /dev/null | > /dev/null \ No newline at end of file