===== Exchange Server Abfrage - Mail und Kalender ===== Auf Smartphone und Computer wird man immer informiert, ob neue mail eingetroffen ist. Damit aber die Heim-Automation auch die aktuellsten Informationen hat, muss man sie sich über z.B. den Exchange-Server besorgen. Mit der [[https://pypi.org/project/exchangelib/|exchangelib]] kann ich vom Raspberry Pi aus den Arbeits-Exchange-Server abfragen. ==== Installation und Probleme ==== sudo apt-get install python3-pip python3 -m pip install exchangelib --user So wenige Zeilen und so ein langer Weg dahin. Erst kam der Fehler Traceback (most recent call last): File "/usr/local/bin/pip", line 7, in from pip import main ImportError: cannot import name main Neu installieren hat nicht geholfen, kann man aber versuchen sudo python3 -m pip uninstall pip && sudo apt install python3-pip --reinstall Weitere Fehlermeldungen Fehlermeldung: Could not install packages due to an EnvironmentError: [Errno 13] Keine Berechtigung: '/usr/local/lib/python3.6/site-packages/dnspython-2.1.0.dist-info' Consider using the `--user` option or check the permissions. Mit sudo davor geht es zwar, aber das ist ja nicht so sicher, daher dann das '--user' hintendrin, was erfolgreich war. Um festzustellen, mit welcher Verschlüsselung der Exchange Server zurecht kommt, kann man einen CURL-Aufruf auf den Webmailer machen. curl -v -I https://webmail.adresse/EWS/Exchange.asmx Nach der Fehlermeldung ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:720) habe ich erst lange viele versuche gemacht, das zu beheben. Eine Idee war, die TLS-Validierung auszuhebeln mit BaseProtocol.HTTP_ADAPTER_CLS = NoVerifyHTTPAdapter hat aber nicht geklappt. Auch mittels pyopenssl und dem Import in urllib3 hat es nicht geklappt. [[https://stackoverflow.com/questions/34075329/what-is-the-urllib3-documentation-telling-me-to-do|Hier]] der Artikel und dann noch die Code-Zeilen sudo python3 -m pip install pyopenssl import OpenSSL import urllib3 import urllib3.contrib.pyopenssl as pyopenssl pyopenssl.inject_into_urllib3() Erst mit dem DEBUG-Log und dem automatischen Discover unseres Exchange-Servers bin ich den richtigen Einstellungen auf die Schliche gekommen. import logging from exchangelib.util import PrettyXmlHandler from exchangelib.protocol import BaseProtocol, NoVerifyHTTPAdapter logging.basicConfig(level=logging.DEBUG, handlers=[PrettyXmlHandler()]) Bei 'unserem' Server gibt es ein Konfiguration-Problem. DEBUG:exchangelib.version:API version "Exchange2013_SP1" worked but server reports version "V2_23". Using "Exchange2013_SP1" DEBUG:exchangelib.services.common:Found new version (Build=None, API=Exchange2019, Fullname=Microsoft Exchange Server 2019 -> Build=15.0.1497.26, API=Exchange2013_SP1, Fullname=Microsoft Exchange Server 2013 SP1) ==== Abfrage der letzten 5 mails in der Inbox ==== Hier der funktionierende Aufruf: import paho.mqtt.client as mqtt import json import time import pytz import requests.auth import logging from datetime import datetime, timedelta, time from exchangelib import DELEGATE, IMPERSONATION, Account, Credentials, EWSDateTime from exchangelib import EWSDate, EWSTimeZone, Configuration, NTLM, GSSAPI from exchangelib import CalendarItem, Message, Mailbox, Attendee, Q, ExtendedProperty from exchangelib import FileAttachment, ItemAttachment, HTMLBody, Build, Version, FolderCollection from exchangelib import transport from exchangelib.util import PrettyXmlHandler from exchangelib.protocol import BaseProtocol, NoVerifyHTTPAdapter #logging.basicConfig(level=logging.DEBUG, handlers=[PrettyXmlHandler()]) creds = Credentials( username="domain\\user", password="geheim" ) version = Version(build=Build(15,0,1497,26)) config = Configuration(service_endpoint='https://mapi.adresse.des.servers/EWS/Exchange.asmx', credentials=creds, version=version) account = Account( primary_smtp_address="mail@adresse", credentials=creds, autodiscover=False, config=config, access_type=DELEGATE ) for item in account.inbox.all().order_by('-datetime_received')[:5]: print(item.subject) Danach habe ich das ganze noch an den MQTT-Server angebunden, damit nur bei Anwesenheit die ungelesenen Mails der letzten paar Stunden vorgelesen werden, sowie ein crontab Aufruf, der das alle 5 Minuten startet. ==== Kalender-Einträge ==== Die Anmeldung an den Exchange-Server ist die gleiche wie oben. Um Kalendereinträge abzufragen braucht es diesen Code. Ein wenig Probleme hat die Zeitzone gemacht. now=datetime.now() current = EWSDateTime(now.year,now.month,now.day,now.hour,00,tzinfo = EWSTimeZone.localzone()) future = current + timedelta(days=1) erg=list(account.calendar.view(start=current, end=future)) mytext="" if not erg: print('No meetings') mytext="keine termine heute" else: mytext="Deine nächsten Termine:" for item in erg: d=item.start.astimezone(EWSTimeZone.localzone()) print(d.strftime("%d-%m-%Y %H:%M"),' - ', item.subject) mytext=mytext+" Um "+d.strftime("%H:%M")+" Uhr hast Du den Termin: "+item.subject+". " Nachdem der String erzeugt wurde, kann ich ihn dann von meinem Mac bzw. SNIPS, vorlesen lassen. Dann noch 'schnell' die Sprachsteuerung, damit ich mit "SNIPS: termine" meine aktuellen Termine vorlesen lassen kann. ==== SNIPS und falsche Intents bzw. Weiterleitung von Intents ==== Es macht leider einen Unterschied, ob ich sage "termine" oder "was sind meine aktuellen termine". Es werden zwei verschiedene hermes-intents aufgerufen. Auch da hat es wieder einige Zeit gedauert, bis ich es gelöst habe. In dem Intentionen, der nicht genutzt werden soll, muss ich eine Weiterleitung machen. Die schnellste Lösung steht hier. Man könnte aber auch aus dem ersten Intention die 'alternative' auslesen, und diesen Intentionen dann Publishen. Das mache ich dann beim nächsten mal. Jetzt steht einfach fester der Name drin. Das ganze ist hier für node.js geschrieben client.publish('hermes/dialogueManager/endSession', JSON.stringify({'sessionId': intent.sessionId})); client.publish('hermes/intent/varlet:erinnerung', JSON.stringify({'input':'termine', 'intent':intent.intent, 'slots':intent.slots, 'id':intent.id, 'sessionId':intent.sessionId, 'customData':intent.customData, 'asrTokens':intent.asrTokens})); Stand Januar 2022